WhiteSevsUtils

一个好用的工具类

当前为 2023-09-09 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/455186/1247772/WhiteSevsUtils.js

  1. /**
  2. * 方便好用的工具类
  3. * @copyright GPL-3.0-only
  4. * @author WhiteSev
  5. **/
  6. (function () {
  7. /* 在window下挂载的对象名 */
  8. const GLOBAL_NAME_SPACE = "Utils";
  9. /* 如果window下存在着Utils,临时保存该对象 */
  10. const originalUtils =
  11. typeof window[GLOBAL_NAME_SPACE] !== "undefined"
  12. ? window[GLOBAL_NAME_SPACE]
  13. : null;
  14.  
  15. const Utils = {
  16. /**
  17. * 工具类的版本
  18. * @type {string}
  19. */
  20. version: "2023-9-9",
  21. };
  22.  
  23. /**
  24. * JSON数据从源端替换到目标端中,如果目标端存在该数据则替换,不添加,返回结果为目标端替换完毕的结果
  25. * @param {object} target 目标端
  26. * @param {object} source 源端
  27. * @returns {object}
  28. * @example
  29. * Utils.assign({"1":1,"2":{"3":3}}, {"2":{"3":4}});
  30. * > {
  31. "1": 1,
  32. "2": {
  33. "3": 4
  34. }
  35. }
  36. **/
  37. Utils.assign = function (target = {}, source = {}) {
  38. if (Array.isArray(source)) {
  39. let canTraverse = source.filter((item) => {
  40. return typeof item === "object";
  41. });
  42. if (!canTraverse.length) {
  43. return source;
  44. }
  45. }
  46. for (let targetKeyName in target) {
  47. let targetValue = target[targetKeyName];
  48. if (targetKeyName in source) {
  49. let sourceValue = source[targetKeyName];
  50. if (typeof sourceValue === "object" && !Utils.isDOM(sourceValue)) {
  51. /* 源端的值是object类型,且不是元素对象 */
  52. if (Object.keys(sourceValue).length) {
  53. target[targetKeyName] = Utils.assign(targetValue, sourceValue);
  54. } else {
  55. target[targetKeyName] = sourceValue;
  56. }
  57. } else {
  58. /* 直接赋值 */
  59. target[targetKeyName] = sourceValue;
  60. }
  61. }
  62. }
  63. return target;
  64. };
  65.  
  66. /**
  67. * 【手机】检测点击的地方是否在该元素区域内
  68. * @param {HTMLElement|Node} obj 需要检测的元素
  69. * @returns {boolean}
  70. * + true 点击在元素上
  71. * + false 未点击在元素上
  72. * @example
  73. * Utils.checkUserClickInNode(document.querySelector(".xxx"));
  74. * > false
  75. **/
  76. Utils.checkUserClickInNode = function (targetNode) {
  77. if (!Utils.isDOM(targetNode)) {
  78. throw new Error(
  79. "Utils.checkUserClickInNode 参数 targetNode 必须为 HTMLElement|Node 类型"
  80. );
  81. }
  82. let mouseClickPosX = Number(window.event.clientX); /* 鼠标相对屏幕横坐标 */
  83. let mouseClickPosY = Number(window.event.clientY); /* 鼠标相对屏幕纵坐标 */
  84. let elementPosXLeft = Number(
  85. targetNode.getBoundingClientRect().left
  86. ); /* 要检测的元素的相对屏幕的横坐标最左边 */
  87. let elementPosXRight = Number(
  88. targetNode.getBoundingClientRect().right
  89. ); /* 要检测的元素的相对屏幕的横坐标最右边 */
  90. let elementPosYTop = Number(
  91. targetNode.getBoundingClientRect().top
  92. ); /* 要检测的元素的相对屏幕的纵坐标最上边 */
  93. let elementPosYBottom = Number(
  94. targetNode.getBoundingClientRect().bottom
  95. ); /* 要检测的元素的相对屏幕的纵坐标最下边 */
  96. let clickNodeHTML = window.event?.target?.innerHTML;
  97. if (
  98. mouseClickPosX >= elementPosXLeft &&
  99. mouseClickPosX <= elementPosXRight &&
  100. mouseClickPosY >= elementPosYTop &&
  101. mouseClickPosY <= elementPosYBottom
  102. ) {
  103. return true;
  104. } else if (clickNodeHTML && targetNode.innerHTML.includes(clickNodeHTML)) {
  105. /* 这种情况是应对在界面中隐藏的元素,getBoundingClientRect获取的都是0 */
  106. return true;
  107. } else {
  108. return false;
  109. }
  110. };
  111.  
  112. /**
  113. * 删除某个父元素,父元素可能在上层或上上层或上上上层...
  114. * @param {HTMLElement|Node} target 当前元素
  115. * @param {string} targetSelector 判断是否满足父元素,参数为当前处理的父元素,满足返回true,否则false
  116. * @returns {boolean}
  117. * + true 已删除
  118. * + false 未删除
  119. * @example
  120. * Utils.deleteParentNode(document.querySelector("a"),".xxx");
  121. * > true
  122. **/
  123. Utils.deleteParentNode = function (target, targetSelector) {
  124. if (target == null) {
  125. throw new Error("Utils.deleteParentNode 参数 target 不能为 null");
  126. }
  127. if (!Utils.isDOM(target)) {
  128. throw new Error(
  129. "Utils.deleteParentNode 参数 target 必须为 Node|HTMLElement 类型"
  130. );
  131. }
  132. if (typeof targetSelector !== "string") {
  133. throw new Error(
  134. "Utils.deleteParentNode 参数 targetSelector 必须为 string 类型"
  135. );
  136. }
  137. let result = false;
  138. let needRemoveDOM = target.closest(targetSelector);
  139. if (needRemoveDOM) {
  140. needRemoveDOM.remove();
  141. result = true;
  142. }
  143. return result;
  144. };
  145.  
  146. /**
  147. * 字典
  148. * @example
  149. * let dictionary = new Utils.Dictionary();
  150. * let dictionary2 = new Utils.Dictionary();
  151. * dictionary.set("test","111");
  152. * dictionary.get("test");
  153. * > '111'
  154. * dictionary.has("test");
  155. * > true
  156. * dictionary.concat(dictionary2);
  157. **/
  158. Utils.Dictionary = function () {
  159. this.items = {};
  160. /**
  161. * 检查是否有某一个键
  162. * @param {any} key 键
  163. * @returns {boolean}
  164. */
  165. this.has = function (key) {
  166. return this.items.hasOwnProperty(key);
  167. };
  168. /**
  169. * 为字典添加某一个值
  170. * @param {any} key 键
  171. * @param {any} val 值,默认为""
  172. */
  173. this.set = function (key, val = "") {
  174. if (key === undefined) {
  175. throw new Error("Utils.Dictionary().set 参数 key 不能为空");
  176. }
  177. this.items[key] = val;
  178. };
  179. /**
  180. * 删除某一个键
  181. * @param {any} key 键
  182. * @returns {boolean}
  183. */
  184. this.delete = function (key) {
  185. if (this.has(key)) {
  186. delete this.items[key];
  187. return true;
  188. }
  189. return false;
  190. };
  191. /**
  192. * 获取某个键的值
  193. * @param {any} key 键
  194. * @returns {any|undefined}
  195. */
  196. this.get = function (key) {
  197. return this.has(key) ? this.items[key] : undefined;
  198. };
  199. /**
  200. * 返回字典中的所有值
  201. * @returns {[...any]}
  202. */
  203. this.values = function () {
  204. let resultList = [];
  205. for (let prop in this.items) {
  206. if (this.has(prop)) {
  207. resultList.push(this.items[prop]);
  208. }
  209. }
  210. return resultList;
  211. };
  212. /**
  213. * 清空字典
  214. */
  215. this.clear = function () {
  216. this.items = {};
  217. };
  218. /**
  219. * 获取字典的长度
  220. * @returns {number}
  221. */
  222. this.size = function () {
  223. return Object.keys(this.items).length;
  224. };
  225. /**
  226. * 获取字典所有的键
  227. * @returns
  228. */
  229. this.keys = function () {
  230. return Object.keys(this.items);
  231. };
  232. /**
  233. * 返回字典本身
  234. * @returns
  235. */
  236. this.getItems = function () {
  237. return this.items;
  238. };
  239. /**
  240. * 合并另一个字典
  241. * @param {Dictionary} data 需要合并的字典
  242. */
  243. this.concat = function (data) {
  244. this.items = Utils.assign(this.items, data.getItems());
  245. };
  246. };
  247.  
  248. /**
  249. * 主动触发事件
  250. * @param {HTMLElement} element 元素
  251. * @param {string|[...string]} eventName 事件名称,可以是字符串,也可是字符串格式的列表
  252. * @param {boolean} 是否使用Proxy代理
  253. * + true 使用Proxy代理Event并设置获取isTrusted永远为True
  254. * + false (默认) 不对Event进行Proxy代理
  255. * @example
  256. * Utils.dispatchEvent(document.querySelector("input","input"))
  257. */
  258. Utils.dispatchEvent = function (element, eventName) {
  259. let eventNameList = [];
  260. if (typeof eventName === "string") {
  261. eventNameList = [eventName];
  262. }
  263. if (Array.isArray(eventName)) {
  264. eventNameList = [...eventName];
  265. }
  266. eventNameList.forEach((_eventName_) => {
  267. let eleEvent = new Event(_eventName_);
  268. element.dispatchEvent(eleEvent);
  269. });
  270. };
  271.  
  272. /**
  273. * 下载base64格式的数据
  274. * @param {string} base64Data 需要转换的base64数据
  275. * @param {string} fileName 需要保存的文件名
  276. * @example
  277. * Utils.downloadBase64("data:image/jpeg:base64/,xxxxxx");
  278. **/
  279. Utils.downloadBase64 = function (base64Data, fileName) {
  280. if (typeof base64Data !== "string") {
  281. throw new Error(
  282. "Utils.downloadBase64 参数 base64Data 必须为 string 类型"
  283. );
  284. }
  285. if (typeof fileName !== "string") {
  286. throw new Error("Utils.downloadBase64 参数 fileName 必须为 string 类型");
  287. }
  288. let aLink = document.createElement("a");
  289. aLink.download = fileName;
  290. aLink.href = base64Data;
  291. aLink.click();
  292. };
  293.  
  294. /**
  295. * 定位网页中可见字符串的位置定位并高亮
  296. * @param {string} str 需要寻找的字符串
  297. * @param {boolean} caseSensitive
  298. * + true 区分大小写
  299. * + false (默认) 不区分大小写
  300. * @return {boolean}
  301. * + true 找到
  302. * + false 未找到
  303. * @example
  304. * Utils.findVisibleText("xxxxx");
  305. * > true
  306. **/
  307. Utils.findVisibleText = function (str = "", caseSensitive = false) {
  308. let TRange = null;
  309. let strFound;
  310. if (window.find) {
  311. /* CODE FOR BROWSERS THAT SUPPORT window.find */
  312. strFound = self.find(str, caseSensitive, true, true, false);
  313. if (strFound && self.getSelection && !self.getSelection().anchorNode) {
  314. strFound = self.find(str, caseSensitive, true, true, false);
  315. }
  316. if (!strFound) {
  317. strFound = self.find(str, 0, 1);
  318. while (self.find(str, 0, 1)) continue;
  319. }
  320. } else if (navigator.appName.indexOf("Microsoft") != -1) {
  321. /* EXPLORER-SPECIFIC CODE */
  322. if (TRange != null) {
  323. TRange.collapse(false);
  324. strFound = TRange.findText(str);
  325. if (strFound) TRange.select();
  326. }
  327. if (TRange == null || strFound == 0) {
  328. TRange = self.document.body.createTextRange();
  329. strFound = TRange.findText(str);
  330. if (strFound) TRange.select();
  331. }
  332. } else if (navigator.appName == "Opera") {
  333. alert("Opera browsers not supported, sorry...");
  334. return;
  335. }
  336. return strFound ? true : false;
  337. };
  338.  
  339. /**
  340. * 格式化byte为KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB
  341. * @param {number} bitSize 字节
  342. * @param {boolean} addType
  343. * + true (默认) 添加单位
  344. * + false 不添加单位
  345. * @returns {string|number}
  346. * + {string} 当addType为true时,且保留小数点末尾2位
  347. * + {number} 当addType为false时,且保留小数点末尾2位
  348. * @example
  349. * Utils.formatByteToSize("812304");
  350. * > '793.27KB'
  351. * @example
  352. * Utils.formatByteToSize("812304",false);
  353. * > 793.27
  354. **/
  355. Utils.formatByteToSize = function (byteSize, addType = true) {
  356. byteSize = parseInt(byteSize);
  357. if (isNaN(byteSize)) {
  358. throw new Error("Utils.formatByteToSize 参数 byteSize 格式不正确");
  359. }
  360. let result = 0;
  361. let resultType = "KB";
  362. let sizeData = {};
  363. sizeData.B = 1;
  364. sizeData.KB = 1024;
  365. sizeData.MB = sizeData.KB * sizeData.KB;
  366. sizeData.GB = sizeData.MB * sizeData.KB;
  367. sizeData.TB = sizeData.GB * sizeData.KB;
  368. sizeData.PB = sizeData.TB * sizeData.KB;
  369. sizeData.EB = sizeData.PB * sizeData.KB;
  370. sizeData.ZB = sizeData.EB * sizeData.KB;
  371. sizeData.YB = sizeData.ZB * sizeData.KB;
  372. sizeData.BB = sizeData.YB * sizeData.KB;
  373. sizeData.NB = sizeData.BB * sizeData.KB;
  374. sizeData.DB = sizeData.NB * sizeData.KB;
  375. for (key in sizeData) {
  376. result = byteSize / sizeData[key];
  377. resultType = key;
  378. if (sizeData.KB >= result) {
  379. break;
  380. }
  381. }
  382. result = result.toFixed(2);
  383. result = addType ? result + resultType.toString() : parseFloat(result);
  384. return result;
  385. };
  386.  
  387. /**
  388. * 应用场景: 当你想要获取数组形式的元素时,它可能是其它的选择器,那么需要按照先后顺序填入参数
  389. * 第一个是优先级最高的,依次下降,如果都没有,返回空列表
  390. * 支持document.querySelectorAll、$("")、()=>{return document.querySelectorAll("")}
  391. * @param {...NodeList|function} NodeList
  392. * @returns {...Element}
  393. * @example
  394. * Utils.getNodeListValue(document.querySelectorAll("div.xxx"),document.querySelectorAll("a.xxx"));
  395. * > [...div,div,div]
  396. * @example
  397. * Utils.getNodeListValue(divGetFunction,aGetFunction);
  398. * > [...div,div,div]
  399. */
  400. Utils.getNodeListValue = function () {
  401. let resultArray = [];
  402. for (let i = 0; i < arguments.length; i++) {
  403. let item = arguments[i];
  404. let value = item;
  405. if (typeof item === "function") {
  406. /* 方法 */
  407. value = item();
  408. }
  409. if (value.length !== 0) {
  410. resultArray = [...value];
  411. break;
  412. }
  413. }
  414. return resultArray;
  415. };
  416.  
  417. /**
  418. * 获取格式化后的时间
  419. * @param {string|undefined} text 需要格式化的字符串或者时间戳
  420. * @param {string|undefined} formatType 格式化成的显示类型
  421. * + yyyy 年
  422. * + MM 月
  423. * + dd 天
  424. * + HH 时 (24小时制)
  425. * + hh 时 (12小时制)
  426. * + mm 分
  427. * + ss 秒
  428. * @returns {string} 返回格式化后的时间
  429. * @example
  430. * Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss");
  431. * > '23:59:00'
  432. * @example
  433. * Utils.formatTime(1899187424988,"HH:mm:ss");
  434. * > '15:10:13'
  435. * @example
  436. * Utils.formatTime()
  437. * > '2023-1-1 00:00:00'
  438. **/
  439. Utils.formatTime = function (text, formatType = "yyyy-MM-dd HH:mm:ss") {
  440. if (text != null && typeof text !== "string" && typeof text !== "number") {
  441. throw new Error("Utils.formatTime 参数 text 必须为 string|number 类型");
  442. }
  443. if (typeof formatType !== "string") {
  444. throw new Error("Utils.formatTime 参数 formatType 必须为 string 类型");
  445. }
  446. let time = text == null ? new Date() : new Date(text);
  447. /**
  448. * 校验时间补0
  449. * @param {number} timeNum
  450. * @returns
  451. */
  452. function checkTime(timeNum) {
  453. if (timeNum < 10) return "0" + timeNum;
  454. return timeNum;
  455. }
  456.  
  457. /**
  458. * 时间制修改 24小时制转12小时制
  459. * @param {number} hourNum 小时
  460. * @returns
  461. */
  462. function timeSystemChange(hourNum) {
  463. return hourNum > 12 ? hourNum - 12 : hourNum;
  464. }
  465.  
  466. let timeRegexp = {
  467. yyyy: time.getFullYear(),
  468. /* 年 */
  469. MM: checkTime(time.getMonth() + 1),
  470. /* 月 */
  471. dd: checkTime(time.getDate()),
  472. /* 日 */
  473. HH: checkTime(time.getHours()),
  474. /* 时 (24小时制) */
  475. hh: checkTime(timeSystemChange(time.getHours())),
  476. /* 时 (12小时制) */
  477. mm: checkTime(time.getMinutes()),
  478. /* 分 */
  479. ss: checkTime(time.getSeconds()),
  480. /* 秒 */
  481. };
  482. Object.keys(timeRegexp).forEach(function (key) {
  483. let replaecRegexp = new RegExp(key, "g");
  484. formatType = formatType.replace(replaecRegexp, timeRegexp[key]);
  485. });
  486. return formatType;
  487. };
  488.  
  489. /**
  490. * 字符串格式的时间转时间戳
  491. * @param {string} text 字符串格式的时间,例如:
  492. * + 2022-11-21 00:00:00
  493. * + 00:00:00
  494. * @return {number} 返回时间戳
  495. * @example
  496. * Utils.formatToTimeStamp("2022-11-21 00:00:00");
  497. * > 1668960000000
  498. **/
  499. Utils.formatToTimeStamp = function (text) {
  500. /* 把字符串格式的时间(完整,包括日期和时间)格式化成时间 */
  501. if (typeof text !== "string") {
  502. throw new Error("Utils.formatToTimeStamp 参数 text 必须为 string 类型");
  503. }
  504. if (text.length === 8) {
  505. /* 该字符串只有时分秒 */
  506. let today = new Date();
  507. text =
  508. today.getFullYear() +
  509. "-" +
  510. (today.getMonth() + 1) +
  511. "-" +
  512. today.getDate() +
  513. " " +
  514. text;
  515. }
  516. text = text.substring(0, 19);
  517. text = text.replace(/-/g, "/");
  518. let timestamp = new Date(text).getTime();
  519. return timestamp;
  520. };
  521.  
  522. /**
  523. * gbk格式的url编码,来自https://gf.qytechs.cn/zh-CN/scripts/427726-gbk-url-js
  524. * @example
  525. * let gbkEncoder = new Utils.GBKEncoder();
  526. * gbkEncoder.encode("测试");
  527. * > '%B2%E2%CA%D4'
  528. * gbkEncoder.decode("%B2%E2%CA%D4");
  529. * > 测试
  530. */
  531. Utils.GBKEncoder = function () {
  532. function handleText(text) {
  533. text = text
  534. .replace(/#(\d+)\$/g, function (a, b) {
  535. return Array(+b + 3).join("#");
  536. })
  537. .replace(/#/g, "####")
  538. .replace(/(\w\w):([\w#]+)(?:,|$)/g, function (a, hd, dt) {
  539. return dt.replace(/../g, function (a) {
  540. if (a != "##") {
  541. return hd + a;
  542. } else {
  543. return a;
  544. }
  545. });
  546. });
  547. return text;
  548. }
  549. function handleHash() {
  550. let index = 0;
  551. data = data.match(/..../g);
  552. for (let i = 0x81; i <= 0xfe; i++) {
  553. for (let j = 0x40; j <= 0xfe; j++) {
  554. U2Ghash[data[index++]] = (
  555. "%" +
  556. i.toString(16) +
  557. "%" +
  558. j.toString(16)
  559. ).toUpperCase();
  560. }
  561. }
  562. for (let key in U2Ghash) {
  563. G2Uhash[U2Ghash[key]] = key;
  564. }
  565. }
  566. function isAscii(unicode) {
  567. return unicode <= 0x007f && unicode >= 0x0000;
  568. }
  569. let data = handleText(
  570. "4e:020405060f12171f20212326292e2f313335373c40414244464a5155575a5b6263646567686a6b6c6d6e6f727475767778797a7b7c7d7f808182838485878a#909697999c9d9ea3aaafb0b1b4b6b7b8b9bcbdbec8cccfd0d2dadbdce0e2e6e7e9edeeeff1f4f8f9fafcfe,4f:00020304050607080b0c12131415161c1d212328292c2d2e31333537393b3e3f40414244454748494a4b4c525456616266686a6b6d6e7172757778797a7d8081828586878a8c8e909293959698999a9c9e9fa1a2a4abadb0b1b2b3b4b6b7b8b9babbbcbdbec0c1c2c6c7c8c9cbcccdd2d3d4d5d6d9dbe0e2e4e5e7ebecf0f2f4f5f6f7f9fbfcfdff,50:000102030405060708090a#0b0e1011131516171b1d1e20222324272b2f303132333435363738393b3d3f404142444546494a4b4d5051525354565758595b5d5e5f6061626364666768696a6b6d6e6f70717273747578797a7c7d818283848687898a8b8c8e8f909192939495969798999a9b9c9d9e9fa0a1a2a4a6aaabadaeafb0b1b3b4b5b6b7b8b9bcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdced0d1d2d3d4d5d7d8d9dbdcdddedfe0e1e2e3e4e5e8e9eaebeff0f1f2f4f6f7f8f9fafcfdfeff,51:00010203040508#090a0c0d0e0f1011131415161718191a1b1c1d1e1f2022232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e42474a4c4e4f5052535758595b5d5e5f606163646667696a6f727a7e7f838486878a8b8e8f90919394989a9d9e9fa1a3a6a7a8a9aaadaeb4b8b9babebfc1c2c3c5c8cacdced0d2d3d4d5d6d7d8d9dadcdedfe2e3e5e6e7e8e9eaeceef1f2f4f7fe,52:0405090b0c0f101314151c1e1f2122232526272a2c2f313234353c3e4445464748494b4e4f5253555758#595a5b5d5f6062636466686b6c6d6e7071737475767778797a7b7c7e808384858687898a8b8c8d8e8f91929495969798999a9ca4a5a6a7aeafb0b4b5b6b7b8b9babbbcbdc0c1c2c4c5c6c8cacccdcecfd1d3d4d5d7d9dadbdcdddee0e1e2e3e5e6e7e8e9eaebecedeeeff1f2f3f4f5f6f7f8fbfcfd,53:0102030407090a0b0c0e11121314181b1c1e1f2224252728292b2c2d2f3031323334353637383c3d404244464b4c4d505458595b5d65686a6c6d7276797b7c7d7e80818387888a8e8f#90919293949697999b9c9ea0a1a4a7aaabacadafb0b1b2b3b4b5b7b8b9babcbdbec0c3c4c5c6c7cecfd0d2d3d5dadcdddee1e2e7f4fafeff,54:000205070b1418191a1c2224252a303336373a3d3f4142444547494c4d4e4f515a5d5e5f6061636567696a6b6c6d6e6f7074797a7e7f8183858788898a8d919397989c9e9fa0a1a2a5aeb0b2b5b6b7b9babcbec3c5cacbd6d8dbe0e1e2e3e4ebeceff0f1f4f5f6f7f8f9fbfe,55:0002030405080a0b0c0d0e121315161718191a1c1d1e1f212526#28292b2d3234353638393a3b3d40424547484b4c4d4e4f515253545758595a5b5d5e5f60626368696b6f7071727374797a7d7f85868c8d8e9092939596979a9b9ea0a1a2a3a4a5a6a8a9aaabacadaeafb0b2b4b6b8babcbfc0c1c2c3c6c7c8cacbcecfd0d5d7d8d9dadbdee0e2e7e9edeef0f1f4f6f8f9fafbfcff,56:0203040506070a0b0d1011121314151617191a1c1d202122252628292a2b2e2f30333537383a3c3d3e404142434445464748494a4b4f5051525355565a5b5d5e5f6061#636566676d6e6f70727374757778797a7d7e7f80818283848788898a8b8c8d9091929495969798999a9b9c9d9e9fa0a1a2a4a5a6a7a8a9aaabacadaeb0b1b2b3b4b5b6b8b9babbbdbebfc0c1c2c3c4c5c6c7c8c9cbcccdcecfd0d1d2d3d5d6d8d9dce3e5e6e7e8e9eaeceeeff2f3f6f7f8fbfc,57:00010205070b0c0d0e0f101112131415161718191a1b1d1e202122242526272b313234353637383c3d3f414344454648494b52535455565859626365676c6e707172747578797a7d7e7f80#818788898a8d8e8f90919495969798999a9c9d9e9fa5a8aaacafb0b1b3b5b6b7b9babbbcbdbebfc0c1c4c5c6c7c8c9cacccdd0d1d3d6d7dbdcdee1e2e3e5e6e7e8e9eaebeceef0f1f2f3f5f6f7fbfcfeff,58:0103040508090a0c0e0f101213141617181a1b1c1d1f222325262728292b2c2d2e2f31323334363738393a3b3c3d3e3f4041424345464748494a4b4e4f505253555657595a5b5c5d5f6061626364666768696a6d6e6f707172737475767778797a7b7c7d7f82848687888a8b8c#8d8e8f909194959697989b9c9da0a1a2a3a4a5a6a7aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbdbebfc0c2c3c4c6c7c8c9cacbcccdcecfd0d2d3d4d6d7d8d9dadbdcdddedfe0e1e2e3e5e6e7e8e9eaedeff1f2f4f5f7f8fafbfcfdfeff,59:000103050608090a0b0c0e1011121317181b1d1e2021222326282c30323335363b3d3e3f404345464a4c4d505253595b5c5d5e5f616364666768696a6b6c6d6e6f70717275777a7b7c7e7f8085898b8c8e8f90919495989a9b9c9d9fa0a1a2a6#a7acadb0b1b3b4b5b6b7b8babcbdbfc0c1c2c3c4c5c7c8c9cccdcecfd5d6d9dbdedfe0e1e2e4e6e7e9eaebedeeeff0f1f2f3f4f5f6f7f8fafcfdfe,5a:00020a0b0d0e0f101214151617191a1b1d1e2122242627282a2b2c2d2e2f3033353738393a3b3d3e3f414243444547484b4c4d4e4f5051525354565758595b5c5d5e5f60616364656668696b6c6d6e6f7071727378797b7c7d7e808182838485868788898a8b8c8d8e8f9091939495969798999c9d9e9fa0a1a2a3a4a5a6a7a8a9abac#adaeafb0b1b4b6b7b9babbbcbdbfc0c3c4c5c6c7c8cacbcdcecfd0d1d3d5d7d9dadbdddedfe2e4e5e7e8eaecedeeeff0f2f3f4f5f6f7f8f9fafbfcfdfeff,5b:0001020304050607080a0b0c0d0e0f10111213141518191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303133353638393a3b3c3d3e3f4142434445464748494a4b4c4d4e4f52565e606167686b6d6e6f7274767778797b7c7e7f82868a8d8e90919294969fa7a8a9acadaeafb1b2b7babbbcc0c1c3c8c9cacbcdcecf#d1d4d5d6d7d8d9dadbdce0e2e3e6e7e9eaebecedeff1f2f3f4f5f6f7fdfe,5c:0002030507080b0c0d0e10121317191b1e1f2021232628292a2b2d2e2f303233353637434446474c4d5253545657585a5b5c5d5f62646768696a6b6c6d70727374757677787b7c7d7e808384858687898a8b8e8f9293959d9e9fa0a1a4a5a6a7a8aaaeafb0b2b4b6b9babbbcbec0c2c3c5c6c7c8c9cacccdcecfd0d1d3d4d5d6d7d8dadbdcdddedfe0e2e3e7e9ebeceeeff1f2f3f4f5f6f7f8f9fafcfdfeff,5d:00#01040508090a0b0c0d0f10111213151718191a1c1d1f2021222325282a2b2c2f3031323335363738393a3b3c3f4041424344454648494d4e4f5051525354555657595a5c5e5f6061626364656667686a6d6e7071727375767778797a7b7c7d7e7f8081838485868788898a8b8c8d8e8f9091929394959697989a9b9c9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b8b9babbbcbdbebfc0c1c2c3c4c6c7c8c9cacbcccecfd0d1d2d3d4d5d6d7d8d9dadcdfe0e3e4eaeced#f0f5f6f8f9fafbfcff,5e:000407090a0b0d0e1213171e1f20212223242528292a2b2c2f303233343536393a3e3f404143464748494a4b4d4e4f50515253565758595a5c5d5f60636465666768696a6b6c6d6e6f70717577797e8182838588898c8d8e92989b9da1a2a3a4a8a9aaabacaeafb0b1b2b4babbbcbdbfc0c1c2c3c4c5c6c7c8cbcccdcecfd0d4d5d7d8d9dadcdddedfe0e1e2e3e4e5e6e7e9ebecedeeeff0f1f2f3f5f8f9fbfcfd,5f:050607090c0d0e10121416191a1c1d1e21222324#282b2c2e30323334353637383b3d3e3f4142434445464748494a4b4c4d4e4f5154595a5b5c5e5f60636567686b6e6f72747576787a7d7e7f83868d8e8f919394969a9b9d9e9fa0a2a3a4a5a6a7a9abacafb0b1b2b3b4b6b8b9babbbebfc0c1c2c7c8cacbced3d4d5dadbdcdedfe2e3e5e6e8e9eceff0f2f3f4f6f7f9fafc,60:0708090b0c10111317181a1e1f2223242c2d2e3031323334363738393a3d3e404445464748494a4c4e4f5153545657585b5c5e5f606165666e71727475777e80#8182858687888a8b8e8f909193959798999c9ea1a2a4a5a7a9aaaeb0b3b5b6b7b9babdbebfc0c1c2c3c4c7c8c9cccdcecfd0d2d3d4d6d7d9dbdee1e2e3e4e5eaf1f2f5f7f8fbfcfdfeff,61:02030405070a0b0c1011121314161718191b1c1d1e21222528292a2c2d2e2f303132333435363738393a3b3c3d3e4041424344454647494b4d4f50525354565758595a5b5c5e5f606163646566696a6b6c6d6e6f717273747678797a7b7c7d7e7f808182838485868788898a8c8d8f9091929395#969798999a9b9c9e9fa0a1a2a3a4a5a6aaabadaeafb0b1b2b3b4b5b6b8b9babbbcbdbfc0c1c3c4c5c6c7c9cccdcecfd0d3d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e7e8e9eaebecedeeeff0f1f2f3f4f6f7f8f9fafbfcfdfe,62:00010203040507091314191c1d1e2023262728292b2d2f303132353638393a3b3c424445464a4f50555657595a5c5d5e5f6061626465687172747577787a7b7d818283858687888b8c8d8e8f9094999c9d9ea3a6a7a9aaadaeafb0b2b3b4b6b7b8babec0c1#c3cbcfd1d5dddee0e1e4eaebf0f2f5f8f9fafb,63:00030405060a0b0c0d0f10121314151718191c2627292c2d2e30313334353637383b3c3e3f40414447484a51525354565758595a5b5c5d60646566686a6b6c6f707273747578797c7d7e7f81838485868b8d9193949597999a9b9c9d9e9fa1a4a6abafb1b2b5b6b9bbbdbfc0c1c2c3c5c7c8cacbccd1d3d4d5d7d8d9dadbdcdddfe2e4e5e6e7e8ebeceeeff0f1f3f5f7f9fafbfcfe,64:0304060708090a0d0e111215161718191a1d1f222324#252728292b2e2f3031323335363738393b3c3e404243494b4c4d4e4f505153555657595a5b5c5d5f60616263646566686a6b6c6e6f70717273747576777b7c7d7e7f8081838688898a8b8c8d8e8f90939497989a9b9c9d9fa0a1a2a3a5a6a7a8aaabafb1b2b3b4b6b9bbbdbebfc1c3c4c6c7c8c9cacbcccfd1d3d4d5d6d9dadbdcdddfe0e1e3e5e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,65:01020304050607080a0b0c0d0e0f10111314151617191a1b1c1d1e1f2021#222324262728292a2c2d30313233373a3c3d404142434446474a4b4d4e5052535457585a5c5f606164656768696a6d6e6f7173757678797a7b7c7d7e7f8081828384858688898a8d8e8f92949596989a9d9ea0a2a3a6a8aaacaeb1b2b3b4b5b6b7b8babbbebfc0c2c7c8c9cacdd0d1d3d4d5d8d9dadbdcdddedfe1e3e4eaebf2f3f4f5f8f9fbfcfdfeff,66:0104050708090b0d1011121617181a1b1c1e2122232426292a2b2c2e3032333738393a3b3d3f40424445464748494a4d4e505158#595b5c5d5e6062636567696a6b6c6d7172737578797b7c7d7f808183858688898a8b8d8e8f909293949598999a9b9c9e9fa0a1a2a3a4a5a6a9aaabacadafb0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8dadedfe0e1e2e3e4e5e7e8eaebecedeeeff1f5f6f8fafbfd,67:010203040506070c0e0f1112131618191a1c1e20212223242527292e303233363738393b3c3e3f414445474a4b4d5254555758595a5b5d62636466676b6c6e717476#78797a7b7d8082838586888a8c8d8e8f9192939496999b9fa0a1a4a6a9acaeb1b2b4b9babbbcbdbebfc0c2c5c6c7c8c9cacbcccdced5d6d7dbdfe1e3e4e6e7e8eaebedeef2f5f6f7f8f9fafbfcfe,68:01020304060d1012141518191a1b1c1e1f20222324252627282b2c2d2e2f30313435363a3b3f474b4d4f52565758595a5b5c5d5e5f6a6c6d6e6f707172737578797a7b7c7d7e7f8082848788898a8b8c8d8e90919294959698999a9b9c9d9e9fa0a1a3a4a5a9aaabacaeb1b2b4b6b7b8#b9babbbcbdbebfc1c3c4c5c6c7c8cacccecfd0d1d3d4d6d7d9dbdcdddedfe1e2e4e5e6e7e8e9eaebecedeff2f3f4f6f7f8fbfdfeff,69:00020304060708090a0c0f11131415161718191a1b1c1d1e21222325262728292a2b2c2e2f313233353637383a3b3c3e4041434445464748494a4b4c4d4e4f50515253555658595b5c5f616264656768696a6c6d6f7072737475767a7b7d7e7f8183858a8b8c8e8f909192939697999a9d9e9fa0a1a2a3a4a5a6a9aaacaeafb0b2b3b5b6b8b9babcbd#bebfc0c2c3c4c5c6c7c8c9cbcdcfd1d2d3d5d6d7d8d9dadcdddee1e2e3e4e5e6e7e8e9eaebeceeeff0f1f3f4f5f6f7f8f9fafbfcfe,6a:000102030405060708090b0c0d0e0f10111213141516191a1b1c1d1e20222324252627292b2c2d2e30323334363738393a3b3c3f40414243454648494a4b4c4d4e4f515253545556575a5c5d5e5f60626364666768696a6b6c6d6e6f70727374757677787a7b7d7e7f81828385868788898a8b8c8d8f929394959698999a9b9c9d9e9fa1a2a3a4a5a6#a7a8aaadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,6b:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f252628292a2b2c2d2e2f303133343536383b3c3d3f4041424445484a4b4d4e4f5051525354555657585a5b5c5d5e5f606168696b6c6d6e6f7071727374757677787a7d7e7f808588#8c8e8f909194959798999c9d9e9fa0a2a3a4a5a6a7a8a9abacadaeafb0b1b2b6b8b9babbbcbdbec0c3c4c6c7c8c9caccced0d1d8dadcdddedfe0e2e3e4e5e6e7e8e9ecedeef0f1f2f4f6f7f8fafbfcfeff,6c:000102030408090a0b0c0e12171c1d1e2023252b2c2d31333637393a3b3c3e3f434445484b4c4d4e4f5152535658595a62636566676b6c6d6e6f71737577787a7b7c7f8084878a8b8d8e9192959697989a9c9d9ea0a2a8acafb0b4b5b6b7bac0c1c2c3c6c7c8cbcdcecfd1d2d8#d9dadcdddfe4e6e7e9ecedf2f4f9ff,6d:000203050608090a0d0f101113141516181c1d1f20212223242628292c2d2f30343637383a3f404244494c50555657585b5d5f6162646567686b6c6d707172737576797a7b7d7e7f8081838486878a8b8d8f9092969798999a9ca2a5acadb0b1b3b4b6b7b9babbbcbdbec1c2c3c8c9cacdcecfd0d2d3d4d5d7dadbdcdfe2e3e5e7e8e9eaedeff0f2f4f5f6f8fafdfeff,6e:0001020304060708090b0f12131518191b1c1e1f222627282a2c2e30313335#3637393b3c3d3e3f40414245464748494a4b4c4f5051525557595a5c5d5e606162636465666768696a6c6d6f707172737475767778797a7b7c7d8081828487888a8b8c8d8e91929394959697999a9b9d9ea0a1a3a4a6a8a9abacadaeb0b3b5b8b9bcbebfc0c3c4c5c6c8c9cacccdced0d2d6d8d9dbdcdde3e7eaebecedeeeff0f1f2f3f5f6f7f8fafbfcfdfeff,6f:000103040507080a0b0c0d0e101112161718191a1b1c1d1e1f212223252627282c2e303234353738393a3b3c3d3f404142#43444548494a4c4e4f5051525354555657595a5b5d5f60616364656768696a6b6c6f707173757677797b7d7e7f808182838586878a8b8f909192939495969798999a9b9d9e9fa0a2a3a4a5a6a8a9aaabacadaeafb0b1b2b4b5b7b8babbbcbdbebfc1c3c4c5c6c7c8cacbcccdcecfd0d3d4d5d6d7d8d9dadbdcdddfe2e3e4e5e6e7e8e9eaebecedf0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,70:000102030405060708090a0b0c0d0e0f1012131415161718191c1d1e1f2021222425262728292a#2b2c2d2e2f30313233343637383a3b3c3d3e3f404142434445464748494a4b4d4e505152535455565758595a5b5c5d5f606162636465666768696a6e7172737477797a7b7d818283848687888b8c8d8f90919397989a9b9e9fa0a1a2a3a4a5a6a7a8a9aab0b2b4b5b6babebfc4c5c6c7c9cbcccdcecfd0d1d2d3d4d5d6d7dadcdddee0e1e2e3e5eaeef0f1f2f3f4f5f6f8fafbfcfeff,71:0001020304050607080b0c0d0e0f111214171b1c1d1e1f2021222324252728292a2b2c2d2e323334#353738393a3b3c3d3e3f4041424344464748494b4d4f505152535455565758595a5b5d5f6061626365696a6b6c6d6f707174757677797b7c7e7f8081828385868788898b8c8d8e909192939596979a9b9c9d9ea1a2a3a4a5a6a7a9aaabadaeafb0b1b2b4b6b7b8babbbcbdbebfc0c1c2c4c5c6c7c8c9cacbcccdcfd0d1d2d3d6d7d8d9dadbdcdddedfe1e2e3e4e6e8e9eaebecedeff0f1f2f3f4f5f6f7f8fafbfcfdfeff,72:0001020304050708090a0b0c0d0e0f101112131415161718191a#1b1c1e1f2021222324252627292b2d2e2f3233343a3c3e40414243444546494a4b4e4f505153545557585a5c5e60636465686a6b6c6d707173747677787b7c7d828385868788898c8e9091939495969798999a9b9c9d9ea0a1a2a3a4a5a6a7a8a9aaabaeb1b2b3b5babbbcbdbebfc0c5c6c7c9cacbcccfd1d3d4d5d6d8dadb#95$,30:000102,00b702:c9c7,00a830:0305,2014ff5e20:162618191c1d,30:141508090a0b0c0d0e0f16171011,00:b1d7f7,22:362728110f2a2908371aa52520,231222:992b2e614c483d1d606e6f64651e3534,26:4240,00b020:3233,2103ff0400a4ff:e0e1,203000a7211626:0605,25:cbcfcec7c6a1a0b3b2,203b21:92909193,30:13#95$,21:70717273747576777879#4$,24:88898a8b8c8d8e8f909192939495969798999a9b7475767778797a7b7c7d7e7f808182838485868760616263646566676869##,32:20212223242526272829##,21:606162636465666768696a6b#97$,ff:010203e505060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5de3#95$,30:4142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f90919293#106$a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6#103$,03:9192939495969798999a9b9c9d9e9fa0a1a3a4a5a6a7a8a9#6$b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c3c4c5c6c7c8c9#5$,fe:3536393a3f403d3e41424344##3b3c373831#3334#104$,04:10111213141501161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f#13$30313233343551363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f#11$,02:cacbd9,20:13152535,21:050996979899,22:151f23526667bf,25:505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727381828384858687#88898a8b8c8d8e8f939495bcbde2e3e4e5,2609229530:121d1e#9$,010100e101ce00e0011300e9011b00e8012b00ed01d000ec014d00f301d200f2016b00fa01d400f901:d6d8dadc,00:fcea,0251e7c701:4448,e7c802:61#2$,31:05060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829#19$,30:212223242526272829,32a333:8e8f9c9d9ea1c4ced1d2d5,fe30ff:e2e4#,212132:31#,20:10#1$,30:fc9b9cfdfe069d9e,fe:494a4b4c4d4e4f50515254555657595a5b5c5d5e5f6061#626364656668696a6b,e7:e7e8e9eaebecedeeeff0f1f2f3,30:07#11$,25:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b#13$,72:dcdddfe2e3e4e5e6e7eaebf5f6f9fdfeff,73:00020405060708090b0c0d0f1011121418191a1f2023242627282d2f30323335363a3b3c3d404142434445464748#494a4b4c4e4f515354555658595a5b5c5d5e5f6162636465666768696a6b6e7071#92$72737475767778797a7b7c7d7f808182838586888a8c8d8f90929394959798999a9c9d9ea0a1a3a4a5a6a7a8aaacadb1b4b5b6b8b9bcbdbebfc1c3c4c5c6c7#cbccced2d3d4d5d6d7d8dadbdcdddfe1e2e3e4e6e8eaebeceeeff0f1f3f4f5f6f7#92$f8f9fafbfcfdfeff,74:0001020407080b0c0d0e1112131415161718191c1d1e1f2021232427292b2d2f31323738393a3b3d3e3f4042434445464748494a4b4c4d#4e4f505152535456585d606162636465666768696a6b6c6e6f717273747578797a#92$7b7c7d7f8284858688898a8c8d8f9192939495969798999a9b9d9fa0a1a2a3a4a5a6aaabacadaeafb0b1b2b3b4b5b6b7b8b9bbbcbdbebfc0c1c2c3c4c5c6c7#c8c9cacbcccdcecfd0d1d3d4d5d6d7d8d9dadbdddfe1e5e7e8e9eaebecedf0f1f2#92$f3f5f8f9fafbfcfdfe,75:0001020305060708090a0b0c0e1012141516171b1d1e202122232426272a2e3436393c3d3f414243444647494a4d5051525355565758#5d5e5f60616263646768696b6c6d6e6f7071737576777a7b7c7d7e808182848587#92$88898a8c8d8e909395989b9c9ea2a6a7a8a9aaadb6b7babbbfc0c1c6cbcccecfd0d1d3d7d9dadcdddfe0e1e5e9ecedeeeff2f3f5f6f7f8fafbfdfe,76:02040607#08090b0d0e0f11121314161a1c1d1e212327282c2e2f31323637393a3b3d414244#92$45464748494a4b4e4f50515253555758595a5b5d5f6061626465666768696a6c6d6e7071727374757677797a7c7f80818385898a8c8d8f9092949597989a9b#9c9d9e9fa0a1a2a3a5a6a7a8a9aaabacadafb0b3b5b6b7b8b9babbbcbdbec0c1c3,554a963f57c3632854ce550954c076:914c,853c77ee827e788d72319698978d6c285b894ffa630966975cb880fa684880ae660276ce51f9655671ac7ff1888450b2596561ca6fb382ad634c625253ed54277b06516b75a45df462d48dcb9776628a8019575d97387f627238767d67cf767e64464f708d2562dc7a17659173ed642c6273822c9881677f724862:6ecc,4f3474e3534a529e7eca90a65e2e6886699c81807ed168d278c5868c9551508d8c2482de80de53058912526576:c4c7c9cbccd3d5d9dadcdddee0e1e2e3e4e6e7e8e9eaebecedf0f3f5f6f7fafbfdff,77:00020305060a0c0e0f1011121314151617181b1c1d1e21232425272a2b#2c2e3031323334393b3d3e3f4244454648494a4b4c4d4e4f52535455565758595c,858496f94fdd582199715b9d62:b1a5,66b48c799c8d7206676f789160b253:5117,8f8880cc8d1d94a1500d72c8590760eb711988ab595482ef672c7b285d297ef7752d6cf58e668ff8903c9f3b6bd491197b145f7c78a784d6853d6b:d5d9d6,5e:0187,75f995ed655d5f:0ac5,8f9f58c181c2907f965b97ad8fb97f168d2c62414fbf53:d85e,8f:a8a9ab,904d68075f6a819888689cd6618b522b762a5f6c658c6fd26ee85bbe644851:75b0,67c44e1979c9997c70b377:5d5e5f606467696a6d6e6f7071727374757677787a7b7c818283868788898a8b8f90939495969798999a9b9c9d9ea1a3a4a6a8abadaeafb1b2b4b6b7b8b9ba#bcbec0c1c2c3c4c5c6c7c8c9cacbcccecfd0d1d2d3d4d5d6d8d9dadddedfe0e1e4,75c55e7673bb83e064ad62e894b56ce2535a52c3640f94c27b944f2f5e1b823681:168a,6e246cca9a736355535c54fa886557e04e0d5e036b657c3f90e8601664e6731c88c16750624d8d22776c8e2991c75f6983dc8521991053c286956b8b60:ede8,707f82:cd31,4ed36ca785cf64cd7cd969fd66f9834953957b564fa7518c6d4b5c428e6d63d253c983:2c36,67e578b4643d5bdf5c945dee8be762c667f48c7a640063ba8749998b8c177f2094f24ea7961098a4660c731677:e6e8eaeff0f1f2f4f5f7f9fafbfc,78:0304050607080a0b0e0f101315191b1e20212224282a2b2e2f31323335363d3f414243444648494a4b4d4f51535458595a#5b5c5e5f606162636465666768696f7071727374757678797a7b7d7e7f80818283,573a5c1d5e38957f507f80a05382655e7545553150218d856284949e671d56326f6e5de2543570928f66626f64a463a35f7b6f8890f481e38fb05c1866685ff16c8996488d81886c649179f057ce6a59621054484e587a0b60e96f848bda627f901e9a8b79e4540375f4630153196c608fdf5f1b9a70803b9f7f4f885c3a8d647fc565a570bd51:45b2,866b5d075ba062bd916c75748e0c7a2061017b794ec77ef877854e1181ed521d51fa6a7153a88e87950496cf6ec19664695a78:848586888a8b8f9092949596999d9ea0a2a4a6a8a9aaabacadaeafb5b6b7b8babbbcbdbfc0c2c3c4c6c7c8cccdcecfd1d2d3d6d7d8dadbdcdddedfe0e1e2e3#e4e5e6e7e9eaebedeeeff0f1f3f5f6f8f9fbfcfdfeff,79:00020304060708090a0b0c,784050a877d7641089e6590463e35ddd7a7f693d4f20823955984e3275ae7a975e:628a,95ef521b5439708a6376952457826625693f918755076df37eaf882262337ef075b5832878c196cc8f9e614874f78bcd6b64523a8d506b21806a847156f153064e:ce1b,51d17c97918b7c074fc38e7f7be17a9c64675d1450ac810676017cb96dec7fe067515b:58f8,78cb64:ae13,63:aa2b,9519642d8fbe7b5476296253592754466b7950a362345e266b864ee38d37888b5f85902e79:0d0e0f1011121415161718191a1b1c1d1f2021222325262728292a2b2c2d2e2f3031323335363738393d3f42434445474a4b4c4d4e4f505152545558596163#6466696a6b6c6e70717273747576797b7c7d7e7f8283868788898b8c8d8e909192,6020803d62c54e39535590f863b880c665e66c2e4f4660ee6de18bde5f3986cb5f536321515a83616863520063638e4850125c9b79775bfc52307a3b60bc905376d75f:b797,76848e6c706f767b7b4977aa51f3909358244f4e6ef48fea654c7b1b72c46da47fdf5ae162b55e95573084827b2c5e1d5f1f90127f1498a063826ec7789870b95178975b57ab75354f4375385e9760e659606dc06bbf788953fc96d551cb52016389540a94938c038dcc7239789f87768fed8c0d53e079:939495969798999b9c9d9e9fa0a1a2a3a4a5a6a8a9aaabacadaeafb0b1b2b4b5b6b7b8bcbfc2c4c5c7c8cacccecfd0d3d4d6d7d9dadbdcdddee0e1e2e5e8ea#eceef1f2f3f4f5f6f7f9fafcfeff,7a:0104050708090a0c0f10111213151618191b1c,4e0176ef53ee948998769f0e952d5b9a8ba24e:221c,51ac846361c252a8680b4f97606b51bb6d1e515c6296659796618c46901775d890fd77636bd272:8aec,8bfb583577798d4c675c9540809a5ea66e2159927aef77ed953b6bb565ad7f0e58065151961f5bf958a954288e726566987f56e4949d76fe9041638754c659:1a3a,579b8eb267358dfa8235524160f0581586fe5ce89e454fc4989d8bb95a2560765384627c904f9102997f6069800c513f80335c1499756d314e8c7a:1d1f21222425262728292a2b2c2d2e2f303132343536383a3e4041424344454748494a4b4c4d4e4f50525354555658595a5b5c5d5e5f606162636465666768#696a6b6c6d6e6f717273757b7c7d7e828587898a8b8c8e8f909394999a9b9ea1a2,8d3053d17f5a7b4f4f104e4f96006cd573d085e95e06756a7ffb6a0a77fe94927e4151e170e653cd8fd483038d2972af996d6cdb574a82b365b980aa623f963259a84eff8bbf7eba653e83f2975e556198de80a5532a8bfd542080ba5e9f6cb88d3982ac915a54296c1b52067eb7575f711a6c7e7c89594b4efd5fff61247caa4e305c0167ab87025cf0950b98ce75af70fd902251af7f1d8bbd594951e44f5b5426592b657780a45b7562:76c2,8f905e456c1f7b264f:0fd8,670d7a:a3a4a7a9aaabaeafb0b1b2b4b5b6b7b8b9babbbcbdbec0c1c2c3c4c5c6c7c8c9cacccdcecfd0d1d2d3d4d5d7d8dadbdcdde1e2e4e7e8e9eaebeceef0f1f2f3#f4f5f6f7f8fbfcfe,7b:0001020507090c0d0e1012131617181a1c1d1f21222327292d,6d:6eaa,798f88b15f17752b629a8f854fef91dc65a781:2f51,5e9c81508d74526f89868d4b590d50854ed8961c723681798d1f5bcc8ba3964459877f1a549056:760e,8be565396982949976d66e895e72751867:46d1,7aff809d8d76611f79c665628d635188521a94a27f38809b7eb25c976e2f67607bd9768b9ad8818f7f947cd5641e95507a3f54:4ae5,6b4c640162089e3d80f3759952729769845b683c86e496:0194,94ec4e2a54047ed968398ddf801566f45e9a7fb97b:2f303234353637393b3d3f404142434446484a4d4e535557595c5e5f61636465666768696a6b6c6d6f70737476787a7c7d7f81828384868788898a8b8c8e8f#9192939698999a9b9e9fa0a3a4a5aeafb0b2b3b5b6b7b9babbbcbdbebfc0c2c3c4,57c2803f68975de5653b529f606d9f9a4f9b8eac516c5bab5f135de96c5e62f18d21517194a952fe6c9f82df72d757a267848d2d591f8f9c83c754957b8d4f306cbd5b6459d19f1353e486ca9aa88c3780a16545987e56fa96c7522e74dc52505be1630289024e5662d0602a68fa51735b9851a089c27ba199867f5060ef704c8d2f51495e7f901b747089c4572d78455f529f9f95fa8f689b3c8be17678684267dc8d:ea35,523d8f8a6eda68cd950590ed56fd679c88f98fc754c87b:c5c8c9cacbcdcecfd0d2d4d5d6d7d8dbdcdedfe0e2e3e4e7e8e9ebecedeff0f2f3f4f5f6f8f9fafbfdff,7c:0001020304050608090a0d0e101112131415171819#1a1b1c1d1e20212223242528292b2c2d2e2f3031323334353637393a3b3c3d3e42,9ab85b696d776c264ea55bb39a87916361a890af97e9542b6db55bd251fd558a7f:55f0,64bc634d65f161be608d710a6c:5749,592f676d822a58d5568e8c6a6beb90dd597d801753f76d695475559d83:77cf,683879be548c4f55540876d28c8996026cb36db88d6b89109e648d3a563f9ed175d55f8872e0606854fc4ea86a2a886160528f7054c470d886799e3f6d2a5b8f5f187ea255894faf7334543c539a501954:0e7c,4e4e5ffd745a58f6846b80e1877472d07cca6e567c:434445464748494a4b4c4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f70717275767778797a7e7f8081828384858687#888a8b8c8d8e8f90939496999a9ba0a1a3a6a7a8a9abacadafb0b4b5b6b7b8babb,5f27864e552c62a44e926caa623782b154d7534e733e6ed1753b521253168bdd69d05f8a60006dee574f6b2273af68538fd87f13636260a3552475ea8c6271156da35ba65e7b8352614c9ec478fa87577c27768751f060f6714c66435e4c604d8c0e707063258f895fbd606286d456de6bc160946167534960e066668d3f79fd4f1a70e96c478b:b3f2,7ed88364660f5a5a9b426d:51f7,8c416d3b4f19706b83b7621660d1970d8d27797851fb57:3efa,673a75787a3d79ef7b957c:bfc0c2c3c4c6c9cbcecfd0d1d2d3d4d8dadbdddee1e2e3e4e5e6e7e9eaebecedeef0f1f2f3f4f5f6f7f9fafcfdfeff,7d:000102030405060708090b0c0d0e0f10#1112131415161718191a1b1c1d1e1f212324252628292a2c2d2e30313233343536,808c99658ff96fc08ba59e2159ec7ee97f095409678168d88f917c4d96c653ca602575be6c7253735ac97ea7632451e0810a5df184df628051805b634f0e796d524260b86d4e5b:c4c2,8b:a1b0,65e25fcc964559937e:e7aa,560967b759394f735bb652a0835a988a8d3e753294be50477a3c4ef767b69a7e5ac16b7c76d1575a5c167b3a95f4714e517c80a9827059787f04832768c067ec78:b177,62e363617b804fed526a51cf835069db92748d:f531,89c1952e7bad4ef67d:3738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6f70717273747576#78797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798,506582305251996f6e:1085,6da75efa50f559dc5c066d466c5f7586848b686859568bb253209171964d854969127901712680f64ea490ca6d479a845a0756bc640594f077eb4fa5811a72e189d2997a7f347ede527f655991758f:7f83,53eb7a9663:eda5,768679f888579636622a52ab8282685467706377776b7aed6d017ed389e359d0621285c982a5754c501f4ecb75a58beb5c4a5dfe7b4b65a491d14eca6d25895f7d2795264ec58c288fdb9773664b79818fd170ec6d787d:999a9b9c9d9e9fa0a1a2a3a4a5a7a8a9aaabacadafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9#dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fa,5c3d52b283465162830e775b66769cb84eac60ca7c:beb3,7ecf4e958b66666f988897595883656c955c5f8475c997567a:dfde,51c070af7a9863ea7a767ea0739697ed4e4570784e5d915253a965:51e7,81fc8205548e5c31759a97a062d872d975bd5c459a7983ca5c40548077e94e3e6cae805a62d2636e5de851778ddd8e1e952f4ff153e560e770ac526763509e435a1f5026773753777ee26485652b628963985014723589c951b38bc07edd574783cc94a7519b541b5cfb7d:fbfcfdfeff,7e:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839#3a3c3d3e3f40424344454648494a4b4c4d4e4f505152535455565758595a5b5c5d,4fca7ae36d5a90e19a8f55805496536154af5f0063e9697751ef6168520a582a52d8574e780d770b5eb761777ce062:5b97,4ea27095800362f770e49760577782db67ef68f578d5989779d158f354b353ef6e34514b523b5ba28bfe80af554357a660735751542d7a7a60505b5463a762a053e362635bc767af54ed7a9f82e691775e9388e4593857ae630e8de880ef57577b774fa95feb5bbd6b3e53217b5072c2684677:ff36,65f751b54e8f76d45cbf7aa58475594e9b4150807e:5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f8081838485868788898a8b8c8d8e8f909192939495969798999a9c9d9e#aeb4bbbcd6e4ecf9,7f:0a101e37393b3c3d3e3f404143464748494a4b4c4d4e4f5253,998861276e8357646606634656f062:ec69,5ed39614578362c955878721814a8fa3556683b167658d5684dd5a6a680f62e67bee961151706f9c8c3063fd89c861d27f0670c26ee57405699472fc5eca90ce67176d6a635e52b3726280014f6c59e5916a70d96d9d52d24e5096f7956d857e78ca7d2f5121579264c2808b7c7b6cea68f1695e51b7539868a872819ece7bf172f879bb6f137406674e91cc9ca4793c83:8954,540f68174e3d538952b1783e5386522950884f:8bd0,7f:56595b5c5d5e6063646566676b6c6d6f7073757677787a7b7c7d7f8082838485868788898b8d8f9091929395969798999b9ca0a2a3a5a6a8a9aaabacadaeb1#b3b4b5b6b7babbbec0c2c3c4c6c7c8c9cbcdcfd0d1d2d3d6d7d9dadbdcdddee2e3,75e27acb7c926ca596b6529b748354e94fe9805483b28fde95705ec9601c6d9f5e18655b813894fe604b70bc7ec37cae51c968817cb1826f4e248f8691cf667e4eae8c0564a9804a50da759771ce5be58fbd6f664e86648295635ed66599521788c270c852a3730e7433679778f797164e3490bb9cde6dcb51db8d41541d62ce73b283f196f69f8494c34f367f9a51cc707596755cad988653e64ee46e9c740969b4786b998f7559521876246d4167f3516d9f99804b54997b3c7abf7f:e4e7e8eaebecedeff2f4f5f6f7f8f9fafdfeff,80:020708090a0e0f11131a1b1d1e1f2123242b2c2d2e2f303234393a3c3e404144454748494e4f505153555657#595b5c5d5e5f6061626364656667686b6c6d6e6f7072737475767778797a7b7c7d,9686578462e29647697c5a0464027bd36f0f964b82a6536298855e90708963b35364864f9c819e93788c97328d:ef42,9e7f6f5e79845f559646622e9a74541594dd4fa365c55c:6561,7f1586516c2f5f8b73876ee47eff5ce6631b5b6a6ee653754e7163a0756562a18f6e4f264ed16ca67eb68bba841d87ba7f57903b95237ba99aa188f8843d6d1b9a867edc59889ebb739b780186829a:6c82,561b541757cb4e709ea653568fc881097792999286ee6ee1851366fc61626f2b80:7e818285888a8d8e8f909192949597999ea3a6a7a8acb0b3b5b6b8b9bbc5c7c8c9cacbcfd0d1d2d3d4d5d8dfe0e2e3e6eef5f7f9fbfeff,81:000103040507080b#0c1517191b1c1d1f202122232425262728292a2b2d2e3033343537393a3b3c3d3f,8c298292832b76f26c135fd983bd732b8305951a6bdb77db94c6536f830251925e3d8c8c8d384e4873ab679a68859176970971646ca177095a9295416bcf7f8e66275bd059b95a9a95:e8f7,4eec84:0c99,6aac76df9530731b68a65b5f772f919a97617cdc8ff78c1c5f257c7379d889c56ccc871c5bc65e4268c977207ef551:954d,52c95a297f05976282d763cf778485d079d26e3a5e9959998511706d6c1162bf76bf654f60af95fd660e879f9e2394ed54:0d7d,8c2c647881:40414243444547494d4e4f525657585b5c5d5e5f6162636466686a6b6c6f727375767778818384858687898b8c8d8e90929394959697999a9e9fa0a1a2a4a5#a7a9abacadaeafb0b1b2b4b5b6b7b8b9bcbdbebfc4c5c7c8c9cbcdcecfd0d1d2d3,647986116a21819c78e864699b5462b9672b83ab58a89ed86cab6f205bde964c8c0b725f67d062c772614ea959c66bcd589366ae5e5552df6155672876ee776672677a4662ff54:ea50,94a090a35a1c7eb36c164e435976801059485357753796be56ca63208111607c95f96dd65462998151855ae980fd59ae9713502a6ce55c3c62df4f60533f817b90066eba852b62c85e7478be64b5637b5ff55a18917f9e1f5c3f634f80425b7d556e95:4a4d,6d8560a867e072de51dd5b8181:d4d5d6d7d8d9dadbdcdddedfe0e1e2e4e5e6e8e9ebeeeff0f1f2f5f6f7f8f9fafdff,82:030708090a0b0e0f111315161718191a1d2024252627292e323a3c3d3f#404142434546484a4c4d4e5051525354555657595b5c5d5e606162636465666769,62e76cde725b626d94ae7ebd81136d53519c5f04597452aa6012597366968650759f632a61e67cef8bfa54e66b279e256bb485d5545550766ca4556a8db4722c5e156015743662cd6392724c5f986e436d3e65006f5876d878d076fc7554522453db4e535e9e65c180:2ad6,629b5486522870ae888d8dd16ce1547880da57f988f48d54966a914d4f696c9b55b776c6783062a870f96f8e5f6d84ec68da787c7bf781a8670b9e4f636778b0576f7812973962:79ab,528874356bd782:6a6b6c6d71757677787b7c808183858687898c90939495969a9b9ea0a2a3a7b2b5b6babbbcbfc0c2c3c5c6c9d0d6d9dadde2e7e8e9eaecedeef0f2f3f5f6f8#fafcfdfeff,83:000a0b0d1012131618191d1e1f20212223242526292a2e3032373b3d,5564813e75b276ae533975de50fb5c418b6c7bc7504f72479a9798d86f0274e27968648777a562fc98918d2b54c180584e52576a82f9840d5e7351ed74f68bc45c4f57616cfc98875a4678349b448feb7c955256625194fa4ec68386846183e984b257d467345703666e6d668c3166dd7011671f6b3a6816621a59bb4e0351c46f0667d26c8f517668cb59476b6775665d0e81109f5065d779:4841,9a918d775c824e5e4f01542f5951780c56686c148fc45f036c:7de3,8bab639083:3e3f41424445484a4b4c4d4e5355565758595d6270717273747576797a7e7f808182838487888a8b8c8d8f909194959697999a9d9fa1a2a3a4a5a6a7acadae#afb5bbbebfc2c3c4c6c8c9cbcdced0d1d2d3d5d7d9dadbdee2e3e4e6e7e8ebeced,60706d3d7275626694:8ec5,53438fc17b7e4edf8c264e7e9ed494:b1b3,524d6f5c90636d458c3458115d4c6b:2049,67aa545b81547f8c589985375f3a62a26a47953965726084686577a74e544fa85de7979864ac7fd85ced4fcf7a8d520783044e14602f7a8394a64fb54eb279e6743452e482b964d279bd5bdd6c8197528f7b6c22503e537f6e0564ce66746c3060c598778bf75e86743c7a7779cb4e1890b174036c4256da914b6cc58d8b533a86c666f28eaf5c489a716e2083:eeeff3f4f5f6f7fafbfcfeff,84:0002050708090a10121314151617191a1b1e1f20212223292a2b2c2d2e2f30323334353637393a3b3e3f404142434445474849#4a4b4c4d4e4f505253545556585d5e5f606264656667686a6e6f70727477797b7c,53d65a369f8b8da353bb570898a76743919b6cc9516875ca62f372ac52:389d,7f3a7094763853749e4a69b7786e96c088d97fa471:36c3,518967d374e458e4651856b78ba9997662707ed560f970ed58ec4e:c1ba,5fcd97e74efb8ba45203598a7eab62544ecd65e5620e833884c98363878d71946eb65bb97ed2519763c967d480898339881551125b7a59828fb14e736c5d516589258f6f962e854a745e95:10f0,6da682e55f3164926d128428816e9cc3585e8d5b4e0953c184:7d7e7f8081838485868a8d8f90919293949596989a9b9d9e9fa0a2a3a4a5a6a7a8a9aaabacadaeb0b1b3b5b6b7bbbcbec0c2c3c5c6c7c8cbcccecfd2d4d5d7#d8d9dadbdcdee1e2e4e7e8e9eaebedeeeff1f2f3f4f5f6f7f8f9fafbfdfe,85:000102,4f1e6563685155d34e2764149a9a626b5ac2745f82726da968ee50e7838e7802674052396c997eb150bb5565715e7b5b665273ca82eb67495c715220717d886b95ea965564c58d6181b355846c5562477f2e58924f2455468d4f664c4e0a5c1a88f368a2634e7a0d70e7828d52fa97f65c1154e890b57ecd59628d4a86c782:0c0d,8d6664445c0461516d89793e8bbe78377533547b4f388eab6df15a207ec5795e6c885ba15a76751a80be614e6e1758f075:1f25,727253477ef385:030405060708090a0b0d0e0f101214151618191b1c1d1e2022232425262728292a2d2e2f303132333435363e3f404142444546474b4c4d4e4f505152535455#57585a5b5c5d5f60616263656667696a6b6c6d6e6f707173757677787c7d7f8081,770176db526980dc57235e08593172ee65bd6e7f8bd75c388671534177f362fe65f64ec098df86805b9e8bc653f277e24f7f5c4e9a7659cb5f0f793a58eb4e1667ff4e8b62ed8a93901d52bf662f55dc566c90024ed54f8d91ca99706c0f5e0260435ba489c68bd56536624b99965b:88ff,6388552e53d77626517d852c67a268b36b8a62928f9353d482126dd1758f4e668d4e5b70719f85af66:91d9,7f7287009ecd9f205c5e672f8ff06811675f620d7ad658855eb665706f3185:82838688898a8b8c8d8e909192939495969798999a9d9e9fa0a1a2a3a5a6a7a9abacadb1b2b3b4b5b6b8babbbcbdbebfc0c2c3c4c5c6c7c8cacbcccdced1d2#d4d6d7d8d9dadbdddedfe0e1e2e3e5e6e7e8eaebecedeeeff0f1f2f3f4f5f6f7f8,60555237800d6454887075295e05681362f4971c53cc723d8c016c3477617a0e542e77ac987a821c8bf47855671470c165af64955636601d79c153f84e1d6b7b80865bfa55e356db4f:3a3c,99725df3677e80386002988290015b8b8b:bcf5,641c825864de55fd82cf91654fd77d20901f7c9f50f358516eaf5bbf8bc980839178849c7b97867d96:8b8f,7ee59ad3788e5c817a57904296a7795f5b59635f7b0b84d168ad55067f2974107d2295016240584c4ed65b835979585485:f9fafcfdfe,86:0001020304060708090a0b0c0d0e0f10121314151718191a1b1c1d1e1f20212223242526282a2b2c2d2e2f3031323334353637393a3b3d3e3f40#4142434445464748494a4b4c525355565758595b5c5d5f6061636465666768696a,736d631e8e:4b0f,80ce82d462ac53f06cf0915e592a60016c70574d644a8d2a762b6ee9575b6a8075f06f6d8c:2d08,57666bef889278b363a253f970ad6c645858642a580268e0819b55107cd650188eba6dcc8d9f70eb638f6d9b6ed47ee68404684390036dd896768ba85957727985e4817e75bc8a8a68af52548e22951163d098988e44557c4f5366ff568f60d56d9552435c4959296dfb586b75:301c,606c82148146631167618fe2773a8d:f334,94c15e165385542c70c386:6d6f7072737475767778838485868788898e8f90919294969798999a9b9e9fa0a1a2a5a6abadaeb2b3b7b8b9bbbcbdbebfc1c2c3c5c8cccdd2d3d5d6d7dadc#dde0e1e2e3e5e6e7e8eaebeceff5f6f7fafbfcfdff,87:010405060b0c0e0f10111416,6c405ef7505c4ead5ead633a8247901a6850916e77b3540c94dc5f647ae5687663457b527edf75db507762955934900f51f879c37a8156fe5f9290146d825c60571f541051546e4d56e263a89893817f8715892a9000541e5c6f81c062:d658,81319e3596409a:6e7c,692d59a562d3553e631654c786d96d3c5a0374e6889c6b6a59168c4c5f2f6e7e73a9987d4e3870f75b8c7897633d665a769660cb5b9b5a494e0781556c6a738b4ea167897f515f8065fa671b5fd859845a0187:191b1d1f20242627282a2b2c2d2f303233353638393a3c3d404142434445464a4b4d4f505152545556585a5b5c5d5e5f6162666768696a6b6c6d6f71727375#7778797a7f8081848687898a8c8e8f90919294959698999a9b9c9d9ea0a1a2a3a4,5dcd5fae537197e68fdd684556f4552f60df4e3a6f4d7ef482c7840e59d44f:1f2a,5c3e7eac672a851a5473754f80c355829b4f4f4d6e2d8c135c096170536b761f6e29868a658795fb7eb9543b7a337d0a95ee55e17fc174ee631d87176da17a9d621165a1536763e16c835deb545c94a84e4c6c618bec5c4b65e0829c68a754:3e34,6b:cb66,4e9463425348821e4f:0dae,575e620a96fe6664726952:ffa1,609f8bef661471996790897f785277fd6670563b54389521727a87:a5a6a7a9aaaeb0b1b2b4b6b7b8b9bbbcbebfc1c2c3c4c5c7c8c9cccdcecfd0d4d5d6d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedeff0f1f2f3f4f5f6f7f8#fafbfcfdff,88:0001020405060708090b0c0d0e0f101112141718191a1c1d1e1f2023,7a00606f5e0c6089819d591560dc718470ef6eaa6c5072806a8488ad5e2d4e605ab3559c94e36d177cfb9699620f7ec6778e867e5323971e8f9666875ce14fa072ed4e0b53a6590f54136380952851484ed99c9c7ea454b88d248854823795f26d8e5f265acc663e966973:b02e,53bf817a99857fa15baa96:7750,7ebf76f853a2957699997bb189446e584e617fd479658be660f354cd4eab98795df76a6150cf54118c618427785d9704524a54ee56a395006d885bb56dc6665388:2425262728292a2b2c2d2e2f30313334353637383a3b3d3e3f414243464748494a4b4e4f505152535556585a5b5c5d5e5f6066676a6d6f717374757678797a#7b7c80838687898a8c8e8f90919394959798999a9b9d9e9fa0a1a3a5a6a7a8a9aa,5c0f5b5d6821809655787b11654869544e9b6b47874e978b534f631f643a90aa659c80c18c10519968b0537887f961c86c:c4fb,8c225c5185aa82af950c6b238f9b65b05f:fbc3,4fe18845661f8165732960fa51745211578b5f6290a2884c91925e78674f602759d351:44f6,80f853086c7996c4718a4f:11ee,7f9e673d55c5950879c088967ee3589f620c9700865a5618987b5f908bb884c4915753d965ed5e8f755c60647d6e5a7f7e:eaed,8f6955a75ba360ac65cb738488:acaeafb0b2b3b4b5b6b8b9babbbdbebfc0c3c4c7c8cacbcccdcfd0d1d3d6d7dadbdcdddee0e1e6e7e9eaebecedeeeff2f5f6f7fafbfdff,89:0001030405060708#090b0c0d0e0f1114151617181c1d1e1f20222324262728292c2d2e2f3132333537,9009766377297eda9774859b5b667a7496ea884052cb718f5faa65ec8be25bfb9a6f5de16b896c5b8b:adaf,900a8fc5538b62bc9e:262d,54404e2b82bd7259869c5d1688596daf96c554d14e9a8bb6710954bd960970df6df976d04e25781487125ca95ef68a00989c960e708e6cbf594463a9773c884d6f148273583071d5538c781a96c155015f6671305bb48c1a9a8c6b83592e9e2f79e76768626c4f6f75a17f8a6d0b96336c274ef075d2517b68376f3e908081705996747689:38393a3b3c3d3e3f40424345464748494a4b4c4d4e4f505152535455565758595a5b5c5d6061626364656768696a6b6c6d6e6f707172737475767778797a7c#7d7e808284858788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1,64475c2790657a918c2359da54ac8200836f898180006930564e8036723791ce51b64e5f987563964e1a53f666f3814b591c6db24e0058f9533b63d694f14f:9d0a,886398905937905779fb4eea80f075916c825b9c59e85f5d69058681501a5df24e5977e34ee5827a6291661390915c794ebf5f7981c69038808475ab4ea688d4610f6bc55fc64e4976ca6ea28b:e3ae,8c0a8bd15f027f:fccc,7ece83:356b,56e06bb797f3963459fb541f94f66deb5bc5996e5c395f15969089:a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c3cdd3d4d5d7d8d9dbdddfe0e1e2e4e7e8e9eaecedeef0f1f2f4f5f6f7f8f9fa#fbfcfdfeff,8a:01020304050608090a0b0c0d0e0f101112131415161718191a1b1c1d,537082f16a315a749e705e947f2883b984:2425,836787478fce8d6276c85f719896786c662054df62e54f6381c375c85eb896cd8e0a86f9548f6cf36d8c6c38607f52c775285e7d4f1860a05fe75c24753190ae94c072b96cb96e389149670953:cbf3,4f5191c98bf153c85e7c8fc26de44e8e76c26986865e611a82064f:59de,903e9c7c61096e:1d14,96854e885a3196e84e0e5c7f79b95b878bed7fbd738957df828b90c15401904755bb5cea5fa161086b3272f180b28a:891e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3f4041424344454647494a4b4c4d4e4f505152535455565758595a5b5c5d5e#5f606162636465666768696a6b6c6d6e6f7071727374757677787a7b7c7d7e7f80,6d745bd388d598848c6b9a6d9e336e0a51:a443,57a38881539f63f48f9556ed54585706733f6e907f188fdc82d1613f6028966266f07ea68d:8ac3,94a55cb37ca4670860a6960580184e9190e75300966851418fd08574915d665597f55b55531d78386742683d54c9707e5bb08f7d518d572854b1651266828d:5e43,810f846c906d7cdf51ff85fb67a365e96fa186a48e81566a90207682707671e58d2362e952196cfd8d3c600e589e618e66fe8d60624e55b36e23672d8f678a:81828384858687888b8c8d8e8f9091929495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2#c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3,94e195f87728680569a8548b4e4d70b88bc86458658b5b857a84503a5be877bb6be18a797c986cbe76cf65a98f975d2d5c5586386808536062187ad96e5b7efd6a1f7ae05f706f335f20638c6da867564e085e108d264ed780c07634969c62db662d627e6cbc8d7571677f695146808753ec906e629854f286f08f998005951785178fd96d5973cd659f771f7504782781fb8d1e94884fa6679575b98bca9707632f9547963584b8632377415f8172f04e896014657462ef6b63653f8a:e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,8b:0001020304050608090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223#24252728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445,5e2775c790d18bc1829d679d652f5431871877e580a281026c414e4b7ec7804c76f4690d6b966267503c4f84574063076b628dbe53ea65e87eb85fd763:1ab7,81:f3f4,7f6e5e1c5cd95236667a79e97a1a8d28709975d46ede6cbb7a924e2d76c55fe0949f88777ec879cd80bf91cd4ef24f17821f54685dde6d328bcc7ca58f7480985e1a549276b15b99663c9aa473e0682a86db6731732a8b:f8db,90107af970db716e62c477a956314e3b845767f152a986c08d2e94f87b518b:464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061626364656768696a6b6d6e6f707172737475767778797a7b7c7d7e7f80818283848586#8788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9facb1bbc7d0ea,8c:091e,4f4f6ce8795d9a7b6293722a62fd4e1378168f6c64b08d5a7bc668695e8488c55986649e58ee72b6690e95258ffd8d5857607f008c0651c6634962d95353684c74228301914c55447740707c6d4a517954a88d4459ff6ecb6dc45b5c7d2b4ed47c7d6ed35b5081ea6e0d5b579b0368d58e2a5b977efc603b7eb590b98d70594f63cd79df8db3535265cf79568bc5963b7ec494bb7e825634918967007f6a5c0a907566285de64f5067de505a4f5c57505e:a7#3$,8c:38393a3b3c3d3e3f4042434445484a4b4d4e4f5051525354565758595b5c5d5e5f60636465666768696c6d6e6f707172747576777b7c7d7e7f808183848687#888b8d8e8f90919293959697999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacad,4e:8d0c,51404e105eff53454e:15981e,9b325b6c56694e2879ba4e3f53154e47592d723b536e6c1056df80e499976bd3777e9f174e:369f,9f104e:5c6993,82885b5b556c560f4ec453:8d9da3a5ae,97658d5d53:1af5262e3e,8d5c53:6663,52:02080e2d333f404c5e615c,84af52:7d82819093,51827f544e:bbc3c9c2e8e1ebde,4f1b4ef34f:2264,4ef54f:2527092b5e67,65384f:5a5d,8c:aeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebec#edeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,8d:000102030405060708090a0b0c0d,4f:5f57323d76749189838f7e7baa7cac94e6e8eac5dae3dcd1dff8,50:294c,4ff350:2c0f2e2d,4ffe50:1c0c25287e4355484e6c7ba5a7a9bad6,510650:edece6ee,51:070b,4edd6c3d4f:5865ce,9fa06c467c74516e5dfd9ec999985181591452f9530d8a07531051eb591951554ea051564eb388:6ea4,4eb5811488d279805b3488037fb851:abb1bdbc,8d:0e0f101112131415161718191a1b1c205152575f6568696a6c6e6f717278797a7b7c7d7e7f808283868788898c8d8e8f90929395969798999a9b9c9d9ea0a1#a2a4a5a6a7a8a9aaabacadaeafb0b2b6b7b9bbbdc0c1c2c5c7c8c9cacdd0d2d3d4,51:c796a2a5,8b:a0a6a7aab4b5b7c2c3cbcfced2d3d4d6d8d9dcdfe0e4e8e9eef0f3f6f9fcff,8c:000204070c0f1112141516191b181d1f202125272a2b2e2f32333536,53:697a,96:1d2221312a3d3c4249545f676c7274888d97b0,90:979b9d99aca1b4b3b6ba,8d:d5d8d9dce0e1e2e5e6e7e9edeef0f1f2f4f6fcfeff,8e:00010203040607080b0d0e1011121315161718191a1b1c202124252627282b2d303233343637383b3c3e#3f4345464c4d4e4f505354555657585a5b5c5d5e5f60616263646567686a6b6e71,90:b8b0cfc5bed0c4c7d3e6e2dcd7dbebeffe,91:04221e23312f394346,520d594252:a2acadbe,54ff52:d0d6f0,53df71ee77cd5ef451:f5fc,9b2f53b65f01755a5def57:4ca9a1,58:7ebcc5d1,57:292c2a33392e2f5c3b4269856b867c7b686d7673ada48cb2cfa7b493a0d5d8dad9d2b8f4eff8e4dd,8e:73757778797a7b7d7e808283848688898a8b8c8d8e91929395969798999a9b9d9fa0a1a2a3a4a5a6a7a8a9aaadaeb0b1b3b4b5b6b7b8b9bbbcbdbebfc0c1c2#c3c4c5c6c7c8c9cacbcccdcfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4,58:0b0d,57:fded,58:001e194420656c81899a80,99a89f1961ff82:797d7f8f8aa8848e919799abb8beb0c8cae398b7aecbccc1a9b4a1aa9fc4cea4e1,830982:f7e4,83:0f07,82:dcf4d2d8,830c82:fbd3,83:111a061415,82:e0d5,83:1c515b5c08923c34319b5e2f4f47435f4017602d3a336665,8e:e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,8f:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223#2425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344,83:681b696c6a6d6eb078b3b4a0aa939c857cb6a97db87b989ea8babcc1,840183:e5d8,580784:180b,83:ddfdd6,84:1c381106,83:d4df,84:0f03,83:f8f9eac5c0,842683:f0e1,84:5c515a597387887a89783c4669768c8e316dc1cdd0e6bdd3cabfbae0a1b9b497e5e3,850c750d853884f085:391f3a,8f:45464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061626364656a808c929da0a1a2a4a5a6a7aaacadaeafb2b3b4b5b7b8babbbcbfc0c3c6#c9cacbcccdcfd2d6d7dae0e1e3e7eceff1f2f4f5f6fafbfcfeff,90:07080c0e131518,85:563b,84:fffc,85:594868645e7a,77a285:43727ba4a8878f79ae9c85b9b7b0d3c1dcff,86:270529163c,5efe5f0859:3c41,803759:555a58,530f5c:22252c34,62:4c6a9fbbcadad7ee,632262f663:394b43adf6717a8eb46dac8a69aebcf2f8e0ffc4dece,645263:c6be,64:45410b1b200c26215e846d96,90:191c2324252728292a2b2c303132333437393a3d3f4043454648494a4b4c4e545556595a5c5d5e5f6061646667696a6b6c6f70717273767778797a7b7c7e81#84858687898a8c8d8e8f90929496989a9c9e9fa0a4a5a7a8a9abadb2b7bcbdbfc0,64:7ab7b899bac0d0d7e4e2,65:09252e,5f:0bd2,75195f1153:5ff1fde9e8fb,54:1216064b5253545643215759233282947771649a9b8476669dd0adc2b4d2a7a6d3d472a3d5bbbfccd9dadca9aaa4ddcfde,551b54e7552054fd551454f355:22230f11272a678fb5496d41553f503c,90:c2c3c6c8c9cbcccdd2d4d5d6d8d9dadedfe0e3e4e5e9eaeceef0f1f2f3f5f6f7f9fafbfcff,91:00010305060708090a0b0c0d0e0f1011121314151617181a1b1c#1d1f20212425262728292a2b2c2d2e30323334353637383a3b3c3d3e3f40414244,55:375675767733305c8bd283b1b988819f7ed6917bdfbdbe9499eaf7c9,561f55:d1ebecd4e6ddc4efe5f2f3cccde8f5e4,8f9456:1e080c012423,55fe56:00272d5839572c4d62595c4c548664716b7b7c8593afd4d7dde1f5ebf9ff,57:040a091c,5e:0f191411313b3c,91:454748515354555658595b5c5f606667686b6d737a7b7c808182838486888a8e8f939495969798999c9d9e9fa0a1a4a5a6a7a8a9abacb0b1b2b3b6b7b8b9bb#bcbdbebfc0c1c2c3c4c5c6c8cbd0d2d3d4d5d6d7d8d9dadbdddedfe0e1e2e3e4e5,5e:3744545b5e61,5c:8c7a8d9096889899919a9cb5a2bdacabb1a3c1b7c4d2e4cbe5,5d:020327262e241e061b583e343d6c5b6f5d6b4b4a697482999d,8c735d:b7c5,5f:73778287898c95999ca8adb5bc,88625f6172:adb0b4b7b8c3c1cecdd2e8efe9f2f4f7,730172f3730372fa91:e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,92:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324#25262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445,72fb73:1713210a1e1d152239252c3831504d57606c6f7e,821b592598e759:2402,99:636768696a6b6c74777d8084878a8d9091939495,5e:80918b96a5a0b9b5beb3,8d535e:d2d1dbe8ea,81ba5f:c4c9d6cf,60035fee60045f:e1e4fe,60:0506,5f:eaedf8,60:1935261b0f0d292b0a3f2178797b7a42,92:464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727375767778797a7b7c7d7e7f808182838485#868788898a8b8c8d8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7,60:6a7d969aad9d83928c9becbbb1ddd8c6dab4,61:20261523,60f461:000e2b4a75ac94a7b7d4f5,5fdd96b395:e9ebf1f3f5f6fcfe,96:030406080a0b0c0d0f12151617191a,4e2c723f62156c:35545c4aa38590948c6869747686a9d0d4adf7f8f1d7b2e0d6faebeeb1d3effe,92:a8a9aaabacadafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8#e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,93:00010203040506070809,6d:39270c43480704190e2b4d2e351a4f525433916f9ea05e93945c607c63,6e1a6d:c7c5de,6e0e6d:bfe0,6e116d:e6ddd9,6e166dab6e0c6dae6e:2b6e4e6bb25f865354322544dfb198e0,6f2d6e:e2a5a7bdbbb7d7b4cf8fc29f,6f:6246472415,6ef96f:2f364b742a0929898d8c78727c7ad1,93:0a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3f40414243444546474849#4a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696b,6f:c9a7b9b6c2e1eedee0ef,70:1a231b39354f5e,5b:80849593a5b8,752f9a9e64345b:e4ee,89305bf08e478b078f:b6d3d5e5eee4e9e6f3e8,90:05040b26110d162135362d2f445152506858625b,66b990:747d8288838b,5f:50575658,5c3b54ab5c:5059,5b715c:6366,7fbc5f:2a292d,82745f3c9b3b5c6e59:81838da9aaa3,93:6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaab#acadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cbcccd,59:97caab9ea4d2b2afd7be,5a:0506,59dd5a0859:e3d8f9,5a:0c09323411231340674a553c6275,80ec5a:aa9b777abeebb2d2d4b8e0e3f1d6e6d8dc,5b:091716323740,5c:151c,5b:5a6573515362,9a:7577787a7f7d808185888a90929396989b9c9d9fa0a2a3a5a7,7e:9fa1a3a5a8a9,93:cecfd0d1d2d3d4d5d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,94:000102030405060708090a0b0c0d#0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e,7e:adb0bec0c1c2c9cbccd0d4d7dbe0e1e8ebeeeff1f2,7f0d7e:f6fafbfe,7f:01020307080b0c0f111217191c1b1f212223242526272a2b2c2d2f3031323335,5e7a757f5ddb753e909573:8e91aea29fcfc2d1b7b3c0c9c8e5d9,987c740a73:e9e7debaf2,74:0f2a5b262528302e2c,94:2f303132333435363738393a3b3c3d3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6c6d6e6f#707172737475767778797a7b7c7d7e7f8081828384919698c7cfd3d4dae6fb,95:1c20,74:1b1a415c575559776d7e9c8e8081878b9ea8a990a7d2ba,97:eaebec,67:4c535e4869a5876a7398a775a89ead8b777cf0,680967d8680a67:e9b0,680c67:d9b5dab3dd,680067:c3b8e2,680e67:c1fd,68:323360614e624464831d55664167403e4a4929b58f7477936bc2,696e68fc69:1f20,68f995:27333d43484b555a606e74757778797a7b7c7d7e808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aa#abacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacb,692468f069:0b0157,68e369:10713960425d846b80987834cc8788ce896663799ba7bbabadd4b1c1cadf95e08dff,6a2f69ed6a:171865,69f26a:443ea0505b358e793d28587c9190a997ab,73:3752,6b:8182878492938d9a9ba1aa,8f:6b6d71727375767877797a7c7e818284878b,95:cccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7ecff,96:0713181b1e20232425262728292b2c2d2f303738393a3e41434a4e4f5152535657#58595a5c5d5e606365666b6d6e6f70717378797a7b7c7d7e7f808182838487898a,8f:8d8e8f989a,8ece62:0b171b1f222125242c,81e774:eff4ff,75:0f1113,65:34eeeff0,66:0a19,677266:031500,708566:f71d34313635,800666:5f54414f56615777848ca79dbedbdce6e9,8d:3233363b3d4045464849474d5559,89:c7cacbcccecfd0d1,72:6e9f5d666f7e7f848b8d8f92,63:0832b0,96:8c8e91929395969a9b9d9e9fa0a1a2a3a4a5a6a8a9aaabacadaeafb1b2b4b5b7b8babbbfc2c3c8cacbd0d1d3d4d6d7d8d9dadbdcdddedfe1e2e3e4e5e6e7eb#ecedeef0f1f2f4f5f8fafbfcfdff,97:0203050a0b0c10111214151718191a1b1d1f20,64:3fd8,80046b:eaf3fdf5f9,6c:0507060d1518191a2129242a32,65:35556b,72:4d525630,8662521680:9f9c93bc,670a80:bdb1abadb4b7e7e8e9eadbc2c4d9cdd7,671080:ddebf1f4ed,81:0d0e,80:f2fc,671581128c5a81:361e2c1832484c5374595a7160697c7d6d67,584d5ab581:888291,6ed581:a3aacc,672681:cabb,97:2122232425262728292b2c2e2f3133343536373a3b3c3d3f404142434445464748494a4b4c4d4e4f5051545557585a5c5d5f63646667686a6b6c6d6e6f7071#72757778797a7b7d7e7f8081828384868788898a8c8e8f9093959697999a9b9c9d,81:c1a6,6b:243739434659,98:d1d2d3d5d9da,6bb35f406bc289f365909f5165:93bcc6c4c3ccced2d6,70:809c969dbbc0b7abb1e8ca,71:1013162f31735c6845724a787a98b3b5a8a0e0d4e7f9,72:1d28,706c71:1866b9,62:3e3d434849,79:3b4046495b5c535a6257606f677a858a9aa7b3,5f:d1d0,97:9e9fa1a2a4a5a6a7a8a9aaacaeb0b1b3b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3#e4e5e8eeeff0f1f2f4f7f8f9fafbfcfdfeff,98:000102030405060708090a0b0c0d0e,60:3c5d5a67415963ab,61:060d5da99dcbd1,620680:807f,6c:93f6,6dfc77:f6f8,78:0009171811,65ab78:2d1c1d393a3b1f3c252c23294e6d56572650474c6a9b939a879ca1a3b2b9a5d4d9c9ecf2,790578f479:13241e34,9f9b9e:f9fbfc,76f177:040d,76f977:07081a22192d263538505147435a68,98:0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d#4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e,77:62657f8d7d808c919fa0b0b5bd,75:3a404e4b485b727983,7f:58615f,8a487f:68747179817e,76:cde5,883294:8586878b8a8c8d8f909497959a9b9ca3a4abaaadacafb0b2b4b6b7b8b9babcbdbfc4c8c9cacbcccdced0d1d2d5d6d7d9d8dbdedfe0e2e4e5e7e8ea,98:6f70717273748b8e929599a3a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcfd0d4d6d7dbdcdde0e1e2e3e4#e5e6e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,99:0001020304050607,94:e9ebeeeff3f4f5f7f9fcfdff,95:03020607090a0d0e0f1213141516181b1d1e1f222a2b292c3132343637383c3e3f4235444546494c4e4f525354565758595b5e5f5d61626465666768696a6b6c6f7172733a,77:e7ec,96c979:d5ede3eb,7a065d477a:03021e14,99:08090a0b0c0e0f1112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2f303132333435363738393a3b3c3d3e3f40414243444546474849#4a4b4c4d4e4f50515253565758595a5b5c5d5e5f60616264667378797b7e828389,7a:393751,9ecf99a57a7076:888e9399a4,74:dee0,752c9e:202228292a2b2c3231363837393a3e414244464748494b4c4e5155575a5b5c5e63666768696a6b6c716d73,75:929496a09daca3b3b4b8c4b1b0c3c2d6cde3e8e6e4ebe7,760375:f1fcff,76:1000050c170a25181519,99:8c8e9a9b9c9d9e9fa0a1a2a3a4a6a7a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8#d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9,76:1b3c2220402d303f35433e334d5e545c566b6f,7fca7a:e6787980868895a6a0aca8adb3,88:6469727d7f82a2c6b7bcc9e2cee3e5f1,891a88:fce8fef0,89:2119131b0a342b3641667b,758b80e576:b2b4,77dc80:1214161c20222526272928310b3543464d526971,898398:788083,99:fafbfcfdfeff,9a:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738#393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556575859,98:898c8d8f949a9b9e9fa1a2a5a6,86:4d546c6e7f7a7c7ba88d8bac9da7a3aa93a9b6c4b5ceb0bab1afc9cfb4e9f1f2edf3d0,871386:def4dfd8d1,87:0307,86f887:080a0d09233b1e252e1a3e48343129373f82227d7e7b60704c6e8b53637c64596593afa8d2,9a:5a5b5c5d5e5f606162636465666768696a6b7283898d8e949599a6a9aaabacadaeafb2b3b4b5b9bbbdbebfc3c4c6c7c8c9cacdcecfd0d2d4d5d6d7d9dadbdc#dddee0e2e3e4e5e7e8e9eaeceef0f1f2f3f4f5f6f7f8fafcfdfeff,9b:000102040506,87:c68885ad9783abe5acb5b3cbd3bdd1c0cadbeae0ee,88:1613,87fe88:0a1b21393c,7f:36424445,82107a:fafd,7b:080304150a2b0f47382a192e31202524333e1e585a45754c5d606e7b62727190a6a7b8ac9da885aa9ca2abb4d1c1ccdddae5e6ea,7c0c7b:fefc,7c:0f160b,9b:07090a0b0c0d0e1011121415161718191a1b1c1d1e2021222425262728292a2b2c2d2e3031333435363738393a3d3e3f40464a4b4c4e50525355565758595a#5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b,7c:1f2a26384140,81fe82:010204,81ec884482:2122232d2f282b383b33343e44494b4f5a5f68,88:7e8588d8df,895e7f:9d9fa7afb0b2,7c7c65497c:919d9c9ea2b2bcbdc1c7cccdc8c5d7e8,826e66a87f:bfced5e5e1e6e9eef3,7cf87d:77a6ae,7e:479b,9e:b8b4,8d:73849491b1676d,8c:4749,91:4a504e4f64,9b:7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9ba#bbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadb,91:626170696f7d7e7274798c85908d91a2a3aaadaeafb5b4ba,8c559e7e8d:b8eb,8e:055969,8d:b5bfbcbac4d6d7dadececfdbc6ecf7f8e3f9fbe4,8e098dfd8e:141d1f2c2e232f3a4039353d3149414251524a70767c6f74858f94909c9e,8c:78828a859894,659b89:d6dedadc,9b:dcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,9c:000102030405060708090a0b0c0d0e0f101112131415161718191a#1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b,89:e5ebef,8a3e8b26975396:e9f3ef,97:0601080f0e2a2d303e,9f:808385868788898a8c,9efe9f:0b0d,96:b9bcbdced2,77bf96e092:8eaec8,93:3e6aca8f,94:3e6b,9c:7f8285868788,7a239c:8b8e90919294959a9b9e9fa0a1a2a3a5a6a7a8a9abadaeb0b1b2b3b4b5b6b7babbbcbdc4c5c6c7cacb3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a#7b7d7e808384898a8c8f93969798999daaacafb9bebfc0c1c2c8c9d1d2dadbe0e1cccdcecfd0d3d4d5d7d8d9dcdddfe2,97:7c85919294afaba3b2b4,9a:b1b0b7,9e589a:b6babcc1c0c5c2cbccd1,9b:45434749484d51,98e899:0d2e5554,9a:dfe1e6efebfbedf9,9b:080f131f23,9e:bdbe,7e3b9e:8287888b92,93d69e:9d9fdbdcdde0dfe2e9e7e5eaef,9f:222c2f39373d3e44,9c:e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,9d:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021#22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142#92$434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f8081#82838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2#92$a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1#e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,9e:000102#92$030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e24272e30343b3c404d5052535456595d5f606162656e6f727475767778797a7b7c7d80#8183848586898a8c8d8e8f90919495969798999a9b9c9ea0a1a2a3a4a5a7a8a9aa#92$abacadaeafb0b1b2b3b5b6b7b9babcbfc0c1c2c3c5c6c7c8cacbccd0d2d3d5d6d7d9dadee1e3e4e6e8ebecedeef0f1f2f3f4f5f6f7f8fafdff,9f:000102030405#060708090a0c0f1112141516181a1b1c1d1e1f21232425262728292a2b2d2e3031#92$3233343536383a3c3f4041424345464748494a4b4c4d4e4f52535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778#797a7b7c7d7e81828d8e8f9091929394959697989c9d9ea1a2a3a4a5,f9:2c7995e7f1#92$,fa:0c0d0e0f111314181f20212324272829,e8:15161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243#4445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061626364"
  571. );
  572. let U2Ghash = {};
  573. let G2Uhash = {};
  574. handleHash();
  575. /**
  576. * 编码
  577. */
  578. this.encode = function (str) {
  579. return [...str].reduce((result, val, i) => {
  580. return result + toGBK(val);
  581. }, "");
  582. function toGBK(val) {
  583. let result = "";
  584. for (let i = 0; i < val.length; i++) {
  585. const codePoint = val.codePointAt(i);
  586. const code = String.fromCodePoint(codePoint);
  587. let key = codePoint.toString(16);
  588. key.length != 4 && (key = ("000" + key).match(/....$/)[0]);
  589.  
  590. // Add up i by code.length
  591. i += code.length - 1;
  592.  
  593. // If code is in ascii range
  594. if (isAscii(codePoint)) {
  595. result += encodeURIComponent(code);
  596. continue;
  597. }
  598.  
  599. // If Got encoded string from U2Ghash
  600. if (U2Ghash[key]) {
  601. result += U2Ghash[key];
  602. continue;
  603. }
  604.  
  605. // If 2 or more char combines to one visible code,
  606. // or just this code is not in GBK
  607. result += toGBK(`&#${codePoint};`);
  608. }
  609. return result;
  610. }
  611. };
  612.  
  613. /**
  614. * 解码
  615. */
  616. this.decode = function (str) {
  617. var GBKMatcher = /%[0-9A-F]{2}%[0-9A-F]{2}/;
  618. var UTFMatcher = /%[0-9A-F]{2}/;
  619. var gbk = true,
  620. utf = true;
  621. while (utf) {
  622. gbk = str.match(GBKMatcher);
  623. utf = str.match(UTFMatcher);
  624. if (gbk && gbk in G2Uhash) {
  625. str = str.replace(gbk, String.fromCharCode("0x" + G2Uhash[gbk]));
  626. } else {
  627. str = str.replace(utf, decodeURIComponent(utf));
  628. }
  629. }
  630. return str;
  631. };
  632. return this;
  633. };
  634.  
  635. /**
  636. * 获取NodeList或Array对象中的最后一个的值
  637. * @param {NodeList|Array} targetObj
  638. * @returns {any}
  639. * @example
  640. * Utils.getArrayLastValue(document.querySelectorAll("div"));
  641. * > div
  642. * @example
  643. * Utils.getArrayLastValue([1,2,3,4,5]);
  644. * > 5
  645. */
  646. Utils.getArrayLastValue = function (targetObj) {
  647. return targetObj[targetObj.length - 1];
  648. };
  649.  
  650. /**
  651. * 使用场景:当想获取的元素可能是不同的选择器的时候,按顺序优先级获取
  652. * 参数类型可以是Element或者是Function
  653. * @returns {any} 如果都没有的话,返回null
  654. * @example
  655. * // 如果a.aaa不存在的话,取a.bbb,这里假设a.aaa不存在
  656. * Utils.getArrayRealValue(document.querySelector("a.aaa"),document.querySelector("a.bbb"));
  657. * > a.bbb
  658. * @example
  659. * Utils.getArrayRealValue(()=>{return document.querySelector("a.aaa").href},()=>{document.querySelector("a.bbb").getAttribute("data-href")});
  660. * > javascript:;
  661. */
  662. Utils.getArrayRealValue = function () {
  663. let result = null;
  664. for (let i = 0; i < arguments.length; i++) {
  665. let item = arguments[i];
  666. if (typeof item === "function") {
  667. /* 方法 */
  668. item = item();
  669. }
  670. if (item != null) {
  671. result = item;
  672. break;
  673. }
  674. }
  675. return result;
  676. };
  677.  
  678. /**
  679. * 获取天数差异,如何获取某个时间与另一个时间相差的天数
  680. * @param {number} timestamp1 时间戳(毫秒|秒)
  681. * @param {number} timestamp2 时间戳(毫秒|秒)
  682. * @param {string} type 返回的数字的表达的类型,比如:年、月、天、时、分、秒,默认天
  683. * @returns {number}
  684. * @example
  685. * Utils.getDaysDifference(new Date().getTime());
  686. * > 0
  687. * @example
  688. * Utils.getDaysDifference(new Date().getTime(),undefined,"秒");
  689. * > 0
  690. */
  691. Utils.getDaysDifference = function (
  692. timestamp1 = new Date().getTime(),
  693. timestamp2 = new Date().getTime(),
  694. type = "天"
  695. ) {
  696. type = type.trim();
  697. if (timestamp1.toString().length === 10) {
  698. timestamp1 = timestamp1 * 1000;
  699. }
  700. if (timestamp2.toString().length === 10) {
  701. timestamp2 = timestamp2 * 1000;
  702. }
  703. let smallTimeStamp = timestamp1 > timestamp2 ? timestamp2 : timestamp1;
  704. let bigTimeStamp = timestamp1 > timestamp2 ? timestamp1 : timestamp2;
  705. let oneSecond = 1000; /* 一秒的毫秒数 */
  706. let oneMinute = 60 * oneSecond; /* 一分钟的毫秒数 */
  707. let oneHour = 60 * oneMinute; /* 一小时的毫秒数 */
  708. let oneDay = 24 * oneHour; /* 一天的毫秒数 */
  709. let oneMonth = 30 * oneDay; /* 一个月的毫秒数(30天) */
  710. let oneYear = 12 * oneMonth; /* 一年的毫秒数 */
  711. let bigDate = new Date(bigTimeStamp);
  712. let smallDate = new Date(smallTimeStamp);
  713. let remainderValue = 1;
  714. if (type === "年") {
  715. remainderValue = oneYear;
  716. } else if (type === "月") {
  717. remainderValue = oneMonth;
  718. } else if (type === "天") {
  719. remainderValue = oneDay;
  720. } else if (type === "时") {
  721. remainderValue = oneHour;
  722. } else if (type === "分") {
  723. remainderValue = oneMinute;
  724. } else if (type === "秒") {
  725. remainderValue = oneSecond;
  726. }
  727. let diffValue = Math.round(
  728. Math.abs((bigDate - smallDate) / remainderValue)
  729. );
  730. return diffValue;
  731. };
  732.  
  733. /**
  734. * 获取元素的选择器字符串
  735. * @param {HTMLElement} element
  736. * @returns {string}
  737. * @example
  738. * Utils.getElementSelector(document.querySelector("a"))
  739. * > '.....'
  740. */
  741. Utils.getElementSelector = function (element) {
  742. if (!element) return;
  743. if (!element.parentElement) return;
  744. /* 如果元素有id属性,则直接返回id选择器 */
  745. if (element.id) return "#" + element.id;
  746.  
  747. /* 递归地获取父元素的选择器 */
  748. let selector = Utils.getElementSelector(element.parentElement);
  749. if (!selector) {
  750. return element.tagName;
  751. }
  752. /* 如果有多个相同类型的兄弟元素,则需要添加索引 */
  753. if (element.parentElement.querySelectorAll(element.tagName).length > 1) {
  754. let index =
  755. Array.prototype.indexOf.call(element.parentElement.children, element) +
  756. 1;
  757. selector +=
  758. " > " + element.tagName.toLowerCase() + ":nth-child(" + index + ")";
  759. } else {
  760. selector += " > " + element.tagName.toLowerCase();
  761. }
  762. return selector;
  763. };
  764.  
  765. /**
  766. * 获取最大值
  767. * @returns {any}
  768. * @example
  769. * Utils.getMaxValue(1,3,5,7,9)
  770. * > 9
  771. * @example
  772. * Utils.getMaxValue([1,3,5])
  773. * > 5
  774. * @example
  775. * Utils.getMaxValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)})
  776. * > 456
  777. * @example
  778. * Utils.getMaxValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)})
  779. * > 2
  780. */
  781. Utils.getMaxValue = function () {
  782. let result = [...arguments];
  783. let newResult = [];
  784. if (result.length > 1) {
  785. if (
  786. result.length === 2 &&
  787. typeof result[0] === "object" &&
  788. typeof result[1] === "function"
  789. ) {
  790. let data = result[0];
  791. let handleDataFunc = result[1];
  792. Object.keys(data).forEach((keyName) => {
  793. newResult = [...newResult, handleDataFunc(keyName, data[keyName])];
  794. });
  795. } else {
  796. result.forEach((item) => {
  797. if (!isNaN(parseFloat(item))) {
  798. newResult = [...newResult, parseFloat(item)];
  799. }
  800. });
  801. }
  802. return Math.max(...newResult);
  803. } else if (result.length === 1) {
  804. result[0].forEach((item) => {
  805. if (!isNaN(parseFloat(item))) {
  806. newResult = [...newResult, parseFloat(item)];
  807. }
  808. });
  809. return Math.max(...newResult);
  810. }
  811. };
  812.  
  813. /**
  814. * 获取页面中最大的z-index再+1
  815. * @returns {number}
  816. * @example
  817. * Utils.getMaxZIndex();
  818. * > 1001
  819. **/
  820. Utils.getMaxZIndex = function () {
  821. let nodeIndexList = [];
  822. document.querySelectorAll("*").forEach((element) => {
  823. let nodeStyle = window.getComputedStyle(element);
  824. /* 不对position为static和display为none的元素进行获取它们的z-index */
  825. if (nodeStyle.position !== "static" && nodeStyle.display !== "none") {
  826. nodeIndexList = nodeIndexList.concat(parseInt(nodeStyle.zIndex));
  827. }
  828. });
  829. nodeIndexList = nodeIndexList.filter(Boolean); /* 过滤非Boolean类型 */
  830. return nodeIndexList.length ? Math.max(...nodeIndexList) + 1 : 0;
  831. };
  832.  
  833. /**
  834. * 获取最小值
  835. * @returns {any}
  836. * @example
  837. * Utils.getMinValue(1,3,5,7,9)
  838. * > 1
  839. * @example
  840. * Utils.getMinValue([1,3,5])
  841. * > 1
  842. * @example
  843. * Utils.getMinValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)})
  844. * > 123
  845. * @example
  846. * Utils.getMinValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)})
  847. * > 0
  848. */
  849. Utils.getMinValue = function () {
  850. let result = [...arguments];
  851. let newResult = [];
  852. if (result.length > 1) {
  853. if (
  854. result.length === 2 &&
  855. typeof result[0] === "object" &&
  856. typeof result[1] === "function"
  857. ) {
  858. let data = result[0];
  859. let handleDataFunc = result[1];
  860. Object.keys(data).forEach((keyName) => {
  861. newResult = [...newResult, handleDataFunc(keyName, data[keyName])];
  862. });
  863. } else {
  864. result.forEach((item) => {
  865. if (!isNaN(parseFloat(item))) {
  866. newResult = [...newResult, parseFloat(item)];
  867. }
  868. });
  869. }
  870. return Math.min(...newResult);
  871. } else if (result.length === 1) {
  872. result[0].forEach((item) => {
  873. if (!isNaN(parseFloat(item))) {
  874. newResult = [...newResult, parseFloat(item)];
  875. }
  876. });
  877. return Math.min(...newResult);
  878. }
  879. };
  880.  
  881. /**
  882. * 获取随机的安卓手机User-Agent
  883. * @return {string} 返回随机字符串
  884. * @example
  885. * Utils.getRandomAndroidUA();
  886. * > 'Mozilla/5.0 (Linux; Android 9; MI 13 Build/OPR1.170623.027; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.3490.40 Mobile Safari/537.36'
  887. **/
  888. Utils.getRandomAndroidUA = function () {
  889. let androidVersion = Utils.getRandomValue(9, 13);
  890. let mobileNameList = [
  891. "LDN-LX3",
  892. "RNE-L03",
  893. "ASUS_X00ID Build/NMF26F",
  894. "WAS-LX3",
  895. "PRA-LX3",
  896. "MYA-L03",
  897. "Moto G Play",
  898. "Moto C Build/NRD90M.063",
  899. "Redmi Note 4 Build/NRD90M",
  900. "HUAWEI VNS-L21 Build/HUAWEIVNS-L21",
  901. "VTR-L09",
  902. "TRT-LX3",
  903. "M2003J15SC Build/RP1A.200720.011; wv",
  904. "MI 13 Build/OPR1.170623.027; wv",
  905. ];
  906. let randomMobile = Utils.getRandomValue(mobileNameList);
  907. let chromeVersion1 = Utils.getRandomValue(100, 113);
  908. let chromeVersion2 = Utils.getRandomValue(2272, 5304);
  909. let chromeVersion3 = Utils.getRandomValue(1, 218);
  910. return `Mozilla/5.0 (Linux; Android ${androidVersion}; ${randomMobile}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.0.${chromeVersion2}.${chromeVersion3} Mobile Safari/537.36`;
  911. };
  912.  
  913. /**
  914. * 获取随机值
  915. * @returns {any}
  916. * @example
  917. * Utils.getRandomValue([1,2,3])
  918. * > 3
  919. * @example
  920. * Utils.getRandomValue({1:"结果1",2:"结果2",3:"结果3"}})
  921. * > 结果2
  922. * @example
  923. * Utils.getRandomValue(1,9)
  924. * > 9
  925. * @example
  926. * Utils.getRandomValue(1,9,6,99)
  927. * > 6
  928. * @example
  929. * Utils.getRandomValue({1:1},{2:2})
  930. * > {1: 1}
  931. * @example
  932. * Utils.getRandomValue()
  933. * > undefined
  934. */
  935. Utils.getRandomValue = function () {
  936. let result = [...arguments];
  937. if (result.length > 1) {
  938. if (
  939. result.length === 2 &&
  940. typeof result[0] === "number" &&
  941. typeof result[1] === "number"
  942. ) {
  943. let leftNumber = result[0] > result[1] ? result[1] : result[0];
  944. let rightNumber = result[0] > result[1] ? result[0] : result[1];
  945. return (
  946. Math.round(Math.random() * (rightNumber - leftNumber)) + leftNumber
  947. );
  948. } else {
  949. return result[Math.floor(Math.random() * result.length)];
  950. }
  951. } else if (result.length === 1) {
  952. let paramData = result[0];
  953. if (Array.isArray(paramData)) {
  954. return paramData[Math.floor(Math.random() * paramData.length)];
  955. } else if (
  956. typeof paramData === "object" &&
  957. Object.keys(paramData).length > 0
  958. ) {
  959. let paramObjDataKey =
  960. Object.keys(paramData)[
  961. Math.floor(Math.random() * Object.keys(paramData).length)
  962. ];
  963. return paramData[paramObjDataKey];
  964. } else {
  965. return paramData;
  966. }
  967. }
  968. };
  969. /**
  970. * 获取随机的电脑端User-Agent
  971. * @return {string} 返回随机字符串
  972. * @example
  973. * Utils.getRandomPCUA();
  974. * > 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5068.19 Safari/537.36'
  975. **/
  976. Utils.getRandomPCUA = function () {
  977. let chromeVersion1 = Utils.getRandomValue(100, 113);
  978. let chromeVersion2 = Utils.getRandomValue(2272, 5304);
  979. let chromeVersion3 = Utils.getRandomValue(1, 218);
  980. return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.0.${chromeVersion2}.${chromeVersion3} Safari/537.36`;
  981. };
  982.  
  983. /**
  984. * 获取元素上的使用React框架的实例属性,目前包括reactFiber、reactProps、reactEvents、reactEventHandlers、reactInternalInstance
  985. * @param {HTMLElement} dom 需要获取的目标元素
  986. * @returns {object}
  987. * @example
  988. * Utils.getReactObj(document.querySelector("input"))?.reactProps?.onChange({target:{value:"123"}});
  989. */
  990. Utils.getReactObj = function (dom) {
  991. let result = {};
  992. Object.keys(dom).forEach((domPropsName) => {
  993. if (domPropsName.startsWith("__react")) {
  994. let propsName = domPropsName.replace(/__(.+)\$.+/i, "$1");
  995. if (propsName in result) {
  996. new Error("重复属性 " + domPropsName);
  997. } else {
  998. result[propsName] = dom[domPropsName];
  999. }
  1000. }
  1001. });
  1002. return result;
  1003. };
  1004.  
  1005. /**
  1006. * 获取文本的字符长度
  1007. * @param {string} text
  1008. * @returns {number}
  1009. * @example
  1010. * Utils.getTextLength("测试文本")
  1011. * > 12
  1012. */
  1013. Utils.getTextLength = function (text) {
  1014. let encoder = new TextEncoder();
  1015. let bytes = encoder.encode(text);
  1016. return bytes.length;
  1017. };
  1018.  
  1019. /**
  1020. * 获取文本占据的空间大小,返回自动的单位,如12 Kb,14 K,20 MB,1 GB
  1021. * @param {string} text 目标字符串
  1022. * @param {boolean} addType
  1023. * + true (默认) 自动添加单位
  1024. * + false 不添加单位
  1025. * @returns {string}
  1026. * @example
  1027. * Utils.getTextStorageSize("测试文本");
  1028. * > '12.00B'
  1029. */
  1030. Utils.getTextStorageSize = function (text, addType = true) {
  1031. return Utils.formatByteToSize(Utils.getTextLength(text), addType);
  1032. };
  1033.  
  1034. /**
  1035. * 在页面中增加style元素,如果html节点存在子节点,添加子节点第一个,反之,添加到html节点的子节点最后一个
  1036. * @param {string} cssText css字符串
  1037. * @returns {HTMLElement} 返回添加的CSS标签
  1038. * @example
  1039. * Utils.GM_addStyle("html{}");
  1040. * > <style type="text/css">html{}</style>
  1041. */
  1042. Utils.GM_addStyle = function (cssText) {
  1043. if (typeof cssText !== "string") {
  1044. throw new Error("Utils.GM_addStyle 参数cssText 必须为String类型");
  1045. }
  1046. let cssNode = document.createElement("style");
  1047. cssNode.setAttribute("type", "text/css");
  1048. cssNode.innerHTML = cssText;
  1049. if (document.documentElement.childNodes.length === 0) {
  1050. /* 插入body后 */
  1051. document.documentElement.appendChild(cssNode);
  1052. } else {
  1053. /* 插入head前面 */
  1054. document.documentElement.insertBefore(
  1055. cssNode,
  1056. document.documentElement.childNodes[0]
  1057. );
  1058. }
  1059. return cssNode;
  1060. };
  1061.  
  1062. /**
  1063. * 对于GM_cookie的兼容写法,当无法使用GM_cookie时可以使用这个,但是并不完全兼容,有些写不出来且限制了httponly是无法访问的
  1064. * @example
  1065. let GM_cookie = new Utils.GM_Cookie();
  1066. GM_cookie.list({name:"xxx_cookie_xxx"},function(cookies,error){
  1067. if (!error) {
  1068. console.log(cookies);
  1069. console.log(cookies.value);
  1070. } else {
  1071. console.error(error);
  1072. }
  1073. });
  1074. GM_cookie.set({name:"xxx_cookie_test_xxx",value:"这是Cookie测试值"},function(error){
  1075. if (error) {
  1076. console.error(error);
  1077. } else {
  1078. console.log('Cookie set successfully.');
  1079. }
  1080. })
  1081. GM_cookie.delete({name:"xxx_cookie_test_xxx"},function(error){
  1082. if (error) {
  1083. console.error(error);
  1084. } else {
  1085. console.log('Cookie set successfully.');
  1086. }
  1087. })
  1088. **/
  1089. Utils.GM_Cookie = function () {
  1090. /**
  1091. * 获取Cookie
  1092. * @param {object} paramDetails
  1093. + url string? 默认为当前的url
  1094. + domain string? 默认为当前的域名(window.location.hostname)
  1095. + name string? 需要检索的Cookie的名字
  1096. + path string? 需要检索的Cookie的路径,默认为"/"
  1097. * @param {function} callback
  1098. + cookies object[]
  1099. + error string|undefined
  1100. */
  1101. this.list = (paramDetails = {}, callback = () => {}) => {
  1102. let resultData = [];
  1103. try {
  1104. let details = {
  1105. url: window.location.href,
  1106. domain: window.location.hostname,
  1107. name: "",
  1108. path: "/",
  1109. };
  1110. details = Utils.assign(details, paramDetails);
  1111. let cookies = document.cookie.split(";");
  1112. cookies.forEach((item) => {
  1113. item = item.trimStart();
  1114. let itemName = item.split("=")[0];
  1115. let itemValue = item.replace(new RegExp("^" + itemName + "="), "");
  1116. let nameRegexp =
  1117. details.name instanceof RegExp
  1118. ? details.name
  1119. : new RegExp("^" + details.name, "g");
  1120. if (itemName.match(nameRegexp)) {
  1121. resultData = [
  1122. ...resultData,
  1123. {
  1124. domain: window.location.hostname,
  1125. expirationDate: undefined,
  1126. hostOnly: true,
  1127. httpOnly: false,
  1128. name: itemName,
  1129. path: "/",
  1130. sameSite: "unspecified",
  1131. secure: true,
  1132. session: false,
  1133. value: itemValue,
  1134. },
  1135. ];
  1136. return;
  1137. }
  1138. });
  1139. callback(resultData);
  1140. } catch (error) {
  1141. callback(resultData, error);
  1142. }
  1143. };
  1144.  
  1145. /**
  1146. * 设置Cookie
  1147. * @param {object} paramDetails
  1148. * @param {function} callback
  1149. */
  1150. this.set = (paramDetails = {}, callback = () => {}) => {
  1151. try {
  1152. let details = {
  1153. url: window.location.href,
  1154. name: "",
  1155. value: "",
  1156. domain: window.location.hostname,
  1157. path: "/",
  1158. secure: true,
  1159. httpOnly: false,
  1160. expirationDate: Math.floor(Date.now()) + 60 * 60 * 24 * 30, // Expires in 30 days
  1161. };
  1162. details = Utils.assign(details, paramDetails);
  1163. let life = details.expirationDate
  1164. ? details.expirationDate
  1165. : Math.floor(Date.now()) + 60 * 60 * 24 * 30;
  1166. let cookieStr =
  1167. details.name +
  1168. "=" +
  1169. decodeURIComponent(details.value) +
  1170. ";expires=" +
  1171. new Date(life).toGMTString() +
  1172. "; path=/";
  1173. document.cookie = cookieStr;
  1174. callback();
  1175. } catch (error) {
  1176. callback(error);
  1177. }
  1178. };
  1179.  
  1180. /**
  1181. * 删除Cookie
  1182. * @param {object} paramDetails
  1183. * @param {function} callback
  1184. */
  1185. this.delete = (paramDetails = {}, callback = () => {}) => {
  1186. try {
  1187. let details = {
  1188. url: window.location.href,
  1189. name: "",
  1190. firstPartyDomain: "",
  1191. };
  1192. details = Utils.assign(details, paramDetails);
  1193. let cookieStr =
  1194. details.name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
  1195. document.cookie = cookieStr;
  1196. callback();
  1197. } catch (error) {
  1198. callback(error);
  1199. }
  1200. };
  1201. };
  1202.  
  1203. /**
  1204. * 注册(不可用)油猴菜单
  1205. * @param {object} data 传递的菜单数据
  1206. * 示例:
  1207. * {
  1208. * text:"", // 菜单按钮文本
  1209. * enable: true, // 菜单按钮的选项是否为开启状态
  1210. * accessKey: "a",
  1211. * autoClose: false, // 点击菜单项后是否应关闭弹出菜单
  1212. * showText: function(text, enable){
  1213. * // 点击菜单后显示的文本,参数text是上面的text值,enable是当前菜单是否开启
  1214. * },
  1215. * callback: function(key, status, event){
  1216. * // 菜单的点击回调,参数key是当前的键名,status是点击的状态enable,event是MouseEvent|KeyboardEvent
  1217. * }
  1218. * }
  1219. * @param {boolean} autoReload 点击该菜单后数据改变后自动重载页面,
  1220. * + true (默认) 开启点击菜单后自动刷新网页
  1221. * + false 关闭点击菜单后自动刷新网页
  1222. * @param {function} _GM_getValue_ 传入油猴函数 GM_getValue
  1223. * @param {function} _GM_setValue_ 传入油猴函数 GM_setValue
  1224. * @param {function} _GM_registerMenuCommand_ 传入油猴函数 GM_registerMenuCommand
  1225. * @param {function} _GM_unregisterMenuCommand_ 传入油猴函数 GM_unregisterMenuCommand
  1226. * @example
  1227. let gm_Menu = new Utils.GM_Menu(
  1228. {
  1229. menu_key:{
  1230. text: "测试按钮",
  1231. enable: true,
  1232. accessKey: "a",
  1233. autoClose: false,
  1234. showText: (text,enable) => {
  1235. return "[" + (enable ? "√" : "×") + "]" + text;
  1236. },
  1237. callback: (key,status,event) => {
  1238. console.log("点击菜单,值修改为",status);
  1239. }
  1240. }
  1241. },
  1242. false,
  1243. GM_getValue,
  1244. GM_setValue,
  1245. GM_registerMenuCommand,
  1246. GM_unregisterMenuCommand);
  1247.  
  1248. // 获取某个菜单项的值
  1249. gm_Menu.get("menu_key");
  1250. > true
  1251.  
  1252. // 添加键为menu_key2的菜单项
  1253. gm_Menu.add({
  1254. menu_key2:{
  1255. text: "测试按钮2",
  1256. enable: false,
  1257. showText: (text,enable) => {
  1258. return "[" + (enable ? "√" : "×") + "]" + text;
  1259. },
  1260. callback: (key,status) => {
  1261. console.log("点击菜单,值修改为",status);
  1262. }
  1263. }
  1264. });
  1265.  
  1266. // 更新键为menu_key的显示文字和点击回调
  1267. gm_Menu.update({
  1268. menu_key:{
  1269. text: "更新后的测试按钮",
  1270. enable: true,
  1271. showText: (text,enable) => {
  1272. return "[" + (enable ? "√" : "×") + "]" + text;
  1273. },
  1274. callback: (key,status) => {
  1275. console.log("点击菜单更新后的测试按钮,新值修改为",status);
  1276. }
  1277. }
  1278. });
  1279.  
  1280. // 删除键为menu_key的菜单
  1281. gm_Menu.delete("menu_key");
  1282.  
  1283. // 删除键为menu_key的菜单
  1284. gm_Menu.delete("menu_key");
  1285. **/
  1286. Utils.GM_Menu = function (
  1287. data = {},
  1288. autoReload = false,
  1289. _GM_getValue_,
  1290. _GM_setValue_,
  1291. _GM_registerMenuCommand_,
  1292. _GM_unregisterMenuCommand_
  1293. ) {
  1294. if (typeof _GM_getValue_ !== "function") {
  1295. throw new Error(
  1296. "Utils.GM_Menu 请在脚本开头加上 @grant GM_getValue,且在第3个位置传入该对象"
  1297. );
  1298. }
  1299. if (typeof _GM_setValue_ !== "function") {
  1300. throw new Error(
  1301. "Utils.GM_Menu 请在脚本开头加上 @grant GM_setValue,且在第4个位置传入该对象"
  1302. );
  1303. }
  1304. if (typeof _GM_registerMenuCommand_ !== "function") {
  1305. throw new Error(
  1306. "Utils.GM_Menu 请在脚本开头加上 @grant GM_registerMenuCommand,且在第5个位置传入该对象"
  1307. );
  1308. }
  1309. if (typeof _GM_unregisterMenuCommand_ !== "function") {
  1310. throw new Error(
  1311. "Utils.GM_Menu 请在脚本开头加上 @grant GM_unregisterMenuCommand,且在第6个位置传入该对象"
  1312. );
  1313. }
  1314. let that = this;
  1315. /**
  1316. * 注册(不可用)的菜单的id
  1317. * @type {...string}
  1318. */
  1319. let menuIdList = [];
  1320. /**
  1321. * 初始化数据
  1322. */
  1323. let init = function () {
  1324. menuIdList = [];
  1325. Object.keys(data).forEach((menuId) => {
  1326. let value = Boolean(_GM_getValue_(menuId, data[menuId]["enable"]));
  1327. data[menuId]["enable"] = value;
  1328. });
  1329. };
  1330.  
  1331. /**
  1332. * 注册(不可用)油猴菜单
  1333. */
  1334. let register = function () {
  1335. Object.keys(data).forEach((menuLocalDataItemKey) => {
  1336. let item = data[menuLocalDataItemKey]; /* 本地存储的键名 */
  1337. let text = item["text"]; /* 文本 */
  1338. let defaultEnable = Boolean(
  1339. _GM_getValue_(menuLocalDataItemKey, item["enable"])
  1340. ); /* 菜单默认开启的状态 */
  1341. let showText =
  1342. typeof item["showText"] === "function"
  1343. ? item["showText"](text, defaultEnable)
  1344. : text; /* 油猴菜单上显示的文本 */
  1345. let clickCallBack = item["callback"]; /* 用户点击后的回调 */
  1346. let menuOptions = {};
  1347. if (typeof item["autoClose"] !== "undefined") {
  1348. /* 点击菜单项后是否应关闭弹出菜单 */
  1349. menuOptions["autoClose"] = item["autoClose"];
  1350. }
  1351. if (typeof item["accessKey"] !== "undefined") {
  1352. /* 菜单项的可选访问键 */
  1353. menuOptions["accessKey"] = item["accessKey"];
  1354. }
  1355. if (typeof item["title"] !== "undefined") {
  1356. /* 菜单项的鼠标悬浮上的工具提示 */
  1357. menuOptions["title"] = item["title"];
  1358. }
  1359. let callbackFunc = function (event) {
  1360. let localEnable = Boolean(
  1361. _GM_getValue_(menuLocalDataItemKey, defaultEnable)
  1362. );
  1363. _GM_setValue_(menuLocalDataItemKey, !localEnable);
  1364. if (typeof clickCallBack === "function") {
  1365. clickCallBack(menuLocalDataItemKey, localEnable, event);
  1366. }
  1367. if (autoReload) {
  1368. window.location.reload();
  1369. } else {
  1370. that.update();
  1371. }
  1372. };
  1373. let menuOptionsLength = Object.keys(menuOptions).length;
  1374. if (menuOptionsLength === 0) {
  1375. let menuId = _GM_registerMenuCommand_(showText, callbackFunc);
  1376. menuIdList = menuIdList.concat(menuId);
  1377. } else if (menuOptionsLength === 1 && "accessKey" in menuOptions) {
  1378. let menuId = _GM_registerMenuCommand_(
  1379. showText,
  1380. callbackFunc,
  1381. menuOptions["accessKey"]
  1382. );
  1383. menuIdList = menuIdList.concat(menuId);
  1384. } else {
  1385. /* 这个是版本 > 4.20.6186才会有的选项 */
  1386. let menuId = _GM_registerMenuCommand_(
  1387. showText,
  1388. callbackFunc,
  1389. menuOptions
  1390. );
  1391. menuIdList = menuIdList.concat(menuId);
  1392. }
  1393. });
  1394. };
  1395. /**
  1396. * 获取键值开启状态
  1397. * @param {string} menuId
  1398. * @returns {Boolean}
  1399. */
  1400. this.get = function (menuId) {
  1401. return data[menuId]["enable"];
  1402. };
  1403. /**
  1404. * 新增菜单数据
  1405. * @param {Object} paramData
  1406. */
  1407. this.add = function (paramData) {
  1408. Object.assign(data, paramData);
  1409. init();
  1410. register();
  1411. };
  1412. /**
  1413. * 更新菜单数据
  1414. * @param {Object} paramData
  1415. */
  1416. this.update = function (paramData) {
  1417. if (paramData) {
  1418. data = Utils.assign(data, paramData);
  1419. }
  1420. Object.values(menuIdList).forEach((menuId) => {
  1421. that.delete(menuId);
  1422. });
  1423. menuIdList = [];
  1424. init();
  1425. register();
  1426. };
  1427. /**
  1428. * 卸载菜单
  1429. * @param {string} menuId 已注册(不可用)的菜单id
  1430. */
  1431. this.delete = function (menuId) {
  1432. _GM_unregisterMenuCommand_(menuId);
  1433. };
  1434. init(); /* 初始化数据 */
  1435. register(); /* 注册(不可用)到油猴菜单中 */
  1436. };
  1437.  
  1438. /**
  1439. * 基于Function prototype,能够勾住和释放任何函数
  1440. *
  1441. * .hook
  1442. * + realFunc {string} 用于保存原始函数的函数名称,用于unHook
  1443. * + hookFunc {string} 替换的hook函数
  1444. * + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1
  1445. * + methodName {string} 匿名函数需显式传入目标函数名eg:this.Begin = function(){....};}
  1446. *
  1447. * .unhook
  1448. * + realFunc {string} 用于保存原始函数的函数名称,用于unHook
  1449. * + funcName {string} 被Hook的函数名称
  1450. * + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1
  1451. * @example
  1452. let hook = new Utils.Hooks();
  1453. hook.initEnv();
  1454. function myFunction(){
  1455. console.log("我自己需要执行的函数");
  1456. }
  1457. function testFunction(){
  1458. console.log("正常执行的函数");
  1459. }
  1460. testFunction.hook(testFunction,myFunction,window);
  1461. **/
  1462. Utils.Hooks = function () {
  1463. this.initEnv = function () {
  1464. Function.prototype.hook = function (realFunc, hookFunc, context) {
  1465. let _context = null; //函数上下文
  1466. let _funcName = null; //函数名
  1467.  
  1468. _context = context || window;
  1469. _funcName = getFuncName(this);
  1470. _context["realFunc_" + _funcName] = this;
  1471.  
  1472. if (
  1473. _context[_funcName].prototype &&
  1474. _context[_funcName].prototype.isHooked
  1475. ) {
  1476. console.log("Already has been hooked,unhook first");
  1477. return false;
  1478. }
  1479. function getFuncName(fn) {
  1480. // 获取函数名
  1481. let strFunc = fn.toString();
  1482. let _regex = /function\s+(\w+)\s*\(/;
  1483. let patten = strFunc.match(_regex);
  1484. if (patten) {
  1485. return patten[1];
  1486. }
  1487. return "";
  1488. }
  1489. try {
  1490. eval(
  1491. "_context[_funcName] = function " +
  1492. _funcName +
  1493. "(){\n" +
  1494. "let args = Array.prototype.slice.call(arguments,0);\n" +
  1495. "let obj = this;\n" +
  1496. "hookFunc.apply(obj,args);\n" +
  1497. "return _context['realFunc_" +
  1498. _funcName +
  1499. "'].apply(obj,args);\n" +
  1500. "};"
  1501. );
  1502. _context[_funcName].prototype.isHooked = true;
  1503. return true;
  1504. } catch (e) {
  1505. console.log("Hook failed,check the params.");
  1506. return false;
  1507. }
  1508. };
  1509. Function.prototype.unhook = function (realFunc, funcName, context) {
  1510. let _context = null;
  1511. let _funcName = null;
  1512. _context = context || window;
  1513. _funcName = funcName;
  1514. if (!_context[_funcName].prototype.isHooked) {
  1515. console.log("No function is hooked on");
  1516. return false;
  1517. }
  1518. _context[_funcName] = _context["realFunc" + _funcName];
  1519. delete _context["realFunc_" + _funcName];
  1520. return true;
  1521. };
  1522. };
  1523. this.cleanEnv = function () {
  1524. if (Function.prototype.hasOwnProperty("hook")) {
  1525. delete Function.prototype.hook;
  1526. }
  1527. if (Function.prototype.hasOwnProperty("unhook")) {
  1528. delete Function.prototype.unhook;
  1529. }
  1530. return true;
  1531. };
  1532. };
  1533.  
  1534. /**
  1535. * 为减少代码量和回调,把GM_xmlhttpRequest封装
  1536. * 文档地址: https://www.tampermonkey.net/documentation.php?ext=iikm
  1537. * 其中onloadstart、onprogress、onreadystatechange是回调形式,onabort、ontimeout、onerror可以设置全局回调函数
  1538. * @example
  1539. let httpx = new Utils.Httpx(GM_xmlhttpRequest);
  1540. let postResp = await httpx.post({
  1541. url:url,
  1542. data:JSON.stringify({
  1543. test:1
  1544. }),
  1545. timeout: 5000
  1546. });
  1547. console.log(postResp);
  1548. > {
  1549. status: true,
  1550. data: {responseText: "...", response: xxx,...},
  1551. msg: "请求完毕",
  1552. type: "onload",
  1553. }
  1554.  
  1555. if(postResp === "onload" && postResp.status){
  1556. // onload
  1557. }else if(postResp === "ontimeout"){
  1558. // ontimeout
  1559. }
  1560. * @example
  1561. // 也可以先配置全局参数
  1562. let httpx = new Utils.Httpx(GM_xmlhttpRequest);
  1563. httpx.config({
  1564. timeout: 5000,
  1565. async: false,
  1566. responseType: "html",
  1567. redirect: "follow",
  1568. })
  1569. // 优先级为 默认details < 全局details < 单独的details
  1570. */
  1571. Utils.Httpx = function (_GM_xmlHttpRequest_) {
  1572. if (typeof _GM_xmlHttpRequest_ !== "function") {
  1573. throw new Error(
  1574. "Utils.Httpx 请先加入@grant GM_xmlhttpRequest在开头且传入该参数"
  1575. );
  1576. }
  1577. let defaultDetails = {
  1578. url: undefined,
  1579. timeout: 5000,
  1580. async: false,
  1581. responseType: undefined,
  1582. headers: undefined,
  1583. data: undefined,
  1584. redirect: undefined,
  1585. cookie: undefined,
  1586. binary: undefined,
  1587. nocache: undefined,
  1588. revalidate: undefined,
  1589. context: undefined,
  1590. overrideMimeType: undefined,
  1591. anonymous: undefined,
  1592. fetch: undefined,
  1593. user: undefined,
  1594. password: undefined,
  1595. onabort: function () {},
  1596. onerror: function () {},
  1597. ontimeout: function () {},
  1598. onloadstart: function () {},
  1599. onreadystatechange: function () {},
  1600. onprogress: function () {},
  1601. };
  1602.  
  1603. /**
  1604. * 发送请求
  1605. * @param {Object} details
  1606. */
  1607. function request(details) {
  1608. _GM_xmlHttpRequest_(details);
  1609. }
  1610.  
  1611. /**
  1612. * 获取请求配置
  1613. * @param {object} method 当前请求方法,默认get
  1614. * @param {object} resolve promise回调
  1615. * @param {object} details 请求配置
  1616. * @returns {object}
  1617. */
  1618. function getRequestDefails(method, resolve, details) {
  1619. return {
  1620. url: details.url || defaultDetails.url,
  1621. method: method || "get",
  1622. timeout: details.timeout || defaultDetails.timeout,
  1623. async: details.async || defaultDetails.async,
  1624. responseType: details.responseType || defaultDetails.responseType,
  1625. headers: details.headers || defaultDetails.headers,
  1626. data: details.data || defaultDetails.data,
  1627. redirect: details.redirect || defaultDetails.redirect,
  1628. cookie: details.cookie || defaultDetails.cookie,
  1629. binary: details.binary || defaultDetails.binary,
  1630. nocache: details.nocache || defaultDetails.nocache,
  1631. revalidate: details.revalidate || defaultDetails.revalidate,
  1632. context: details.context || defaultDetails.context,
  1633. overrideMimeType:
  1634. details.overrideMimeType || defaultDetails.overrideMimeType,
  1635. anonymous: details.anonymous || defaultDetails.anonymous,
  1636. fetch: details.fetch || defaultDetails.fetch,
  1637. user: details.user || defaultDetails.user,
  1638. password: details.password || defaultDetails.password,
  1639. onabort: function () {
  1640. onAbortCallBack(details, resolve, arguments);
  1641. },
  1642. onerror: function () {
  1643. onErrorCallBack(details, resolve, arguments);
  1644. },
  1645. onloadstart: function () {
  1646. onLoadStartCallBack(details, arguments);
  1647. },
  1648. onprogress: function () {
  1649. onProgressCallBack(details, arguments);
  1650. },
  1651. onreadystatechange: function () {
  1652. onReadyStateChangeCallBack(details, arguments);
  1653. },
  1654. ontimeout: function () {
  1655. onTimeoutCallBack(details, resolve, arguments);
  1656. },
  1657. onload: function () {
  1658. onLoadCallBack(details, resolve, arguments);
  1659. },
  1660. };
  1661. }
  1662. /**
  1663. * 处理发送请求的details,去除值为undefined、空function的值
  1664. * @param {object} details
  1665. * @returns {object}
  1666. */
  1667. function handleRequestDetails(details) {
  1668. Object.keys(details).forEach((keyName) => {
  1669. if (
  1670. details[keyName] == null ||
  1671. (details[keyName] instanceof Function &&
  1672. Utils.isNull(details[keyName]))
  1673. ) {
  1674. delete details[keyName];
  1675. return;
  1676. }
  1677. });
  1678. if (Utils.isNull(details.url)) {
  1679. throw Error(`Utils.Httpx 参数 url不符合要求: ${details.url}`);
  1680. }
  1681. /* method值统一大写,兼容Via */
  1682. details.method = details.method.toUpperCase();
  1683. return details;
  1684. }
  1685.  
  1686. /**
  1687. * onabort请求被取消-触发
  1688. * @param {object} details 配置
  1689. * @param {object} resolve 回调
  1690. * @param {object} argumentsList 参数列表
  1691. */
  1692. function onAbortCallBack(details, resolve, argumentsList) {
  1693. if ("onabort" in details) {
  1694. details.onabort.apply(this, argumentsList);
  1695. } else if ("onabort" in defaultDetails) {
  1696. defaultDetails.onabort.apply(this, argumentsList);
  1697. }
  1698. resolve({
  1699. status: false,
  1700. data: argumentsList,
  1701. msg: "请求被取消",
  1702. type: "onabort",
  1703. });
  1704. }
  1705.  
  1706. /**
  1707. * onerror请求异常-触发
  1708. * @param {object} details 配置
  1709. * @param {object} resolve 回调
  1710. * @param {object} argumentsList 响应的参数列表
  1711. */
  1712. function onErrorCallBack(details, resolve, argumentsList) {
  1713. if ("onerror" in details) {
  1714. details.onerror.apply(this, argumentsList);
  1715. } else if ("onerror" in defaultDetails) {
  1716. defaultDetails.onerror.apply(this, argumentsList);
  1717. }
  1718. resolve({
  1719. status: false,
  1720. data: argumentsList,
  1721. msg: "请求异常",
  1722. type: "onerror",
  1723. });
  1724. }
  1725. /**
  1726. * ontimeout请求超时-触发
  1727. * @param {object} details 配置
  1728. * @param {object} resolve 回调
  1729. * @param {object} argumentsList 参数列表
  1730. */
  1731. function onTimeoutCallBack(details, resolve, argumentsList) {
  1732. if ("ontimeout" in details) {
  1733. details.ontimeout.apply(this, argumentsList);
  1734. } else if ("ontimeout" in defaultDetails) {
  1735. defaultDetails.ontimeout.apply(this, argumentsList);
  1736. }
  1737. resolve({
  1738. status: false,
  1739. data: argumentsList,
  1740. msg: "请求超时",
  1741. type: "ontimeout",
  1742. });
  1743. }
  1744.  
  1745. /**
  1746. * onloadstart请求开始-触发
  1747. * @param {object} details 配置
  1748. * @param {object} argumentsList 参数列表
  1749. */
  1750. function onLoadStartCallBack(details, argumentsList) {
  1751. if ("onloadstart" in details) {
  1752. details.onloadstart.apply(this, argumentsList);
  1753. } else if ("onloadstart" in defaultDetails) {
  1754. defaultDetails.onloadstart.apply(this, argumentsList);
  1755. }
  1756. }
  1757.  
  1758. /**
  1759. * onreadystatechange准备状态改变-触发
  1760. * @param {object} details 配置
  1761. * @param {object} argumentsList 参数列表
  1762. */
  1763. function onReadyStateChangeCallBack(details, argumentsList) {
  1764. if ("onreadystatechange" in details) {
  1765. details.onreadystatechange.apply(this, argumentsList);
  1766. } else if ("onreadystatechange" in defaultDetails) {
  1767. defaultDetails.onreadystatechange.apply(this, argumentsList);
  1768. }
  1769. }
  1770.  
  1771. /**
  1772. * onprogress上传进度-触发
  1773. * @param {object} details 配置
  1774. * @param {object} argumentsList 参数列表
  1775. */
  1776. function onProgressCallBack(details, argumentsList) {
  1777. if ("onprogress" in details) {
  1778. details.onprogress.apply(this, argumentsList);
  1779. } else if ("onprogress" in defaultDetails) {
  1780. defaultDetails.onprogress.apply(this, argumentsList);
  1781. }
  1782. }
  1783.  
  1784. /**
  1785. * onload加载完毕-触发
  1786. * @param {object} details 配置
  1787. * @param {object} resolve 回调
  1788. * @param {object} response 响应
  1789. */
  1790. function onLoadCallBack(details, resolve, argumentsList) {
  1791. /* X浏览器会因为设置了responseType导致不返回responseText */
  1792. let response = argumentsList[0];
  1793. if (
  1794. details.responseType === "json" &&
  1795. Utils.isNull(response.responseText) &&
  1796. typeof response.response === "object"
  1797. ) {
  1798. Utils.tryCatch().run(() => {
  1799. response.responseText = JSON.stringify(response.response);
  1800. });
  1801. }
  1802. if (response.status === 200) {
  1803. resolve({
  1804. status: true,
  1805. data: response,
  1806. msg: "请求完毕",
  1807. type: "onload",
  1808. });
  1809. } else {
  1810. onErrorCallBack(details, resolve, argumentsList);
  1811. }
  1812. }
  1813.  
  1814. /**
  1815. * GET 请求
  1816. * @param {object} details
  1817. * @returns {Promise}
  1818. */
  1819. this.get = function (details) {
  1820. return new Promise((resolve) => {
  1821. let requestDetails = getRequestDefails("get", resolve, details);
  1822. delete requestDetails.onprogress;
  1823. requestDetails = handleRequestDetails(requestDetails);
  1824. request(requestDetails);
  1825. });
  1826. };
  1827. /**
  1828. * POST 请求
  1829. * @param {object} details
  1830. * @returns {Promise}
  1831. */
  1832. this.post = function (details) {
  1833. return new Promise((resolve) => {
  1834. let requestDetails = getRequestDefails("post", resolve, details);
  1835. requestDetails = handleRequestDetails(requestDetails);
  1836. request(requestDetails);
  1837. });
  1838. };
  1839. /**
  1840. * HEAD 请求
  1841. * @param {object} details
  1842. * @returns {Promise}
  1843. */
  1844. this.head = function (details) {
  1845. return new Promise((resolve) => {
  1846. let requestDetails = getRequestDefails("head", resolve, details);
  1847. delete requestDetails.onprogress;
  1848. requestDetails = handleRequestDetails(requestDetails);
  1849. request(requestDetails);
  1850. });
  1851. };
  1852.  
  1853. /**
  1854. * OPTIONS请求
  1855. * @param {object} details
  1856. * @returns {Promise}
  1857. */
  1858. this.options = function (details) {
  1859. return new Promise((resolve) => {
  1860. let requestDetails = getRequestDefails("options", resolve, details);
  1861. delete requestDetails.onprogress;
  1862. requestDetails = handleRequestDetails(requestDetails);
  1863. request(requestDetails);
  1864. });
  1865. };
  1866.  
  1867. /**
  1868. * DELETE请求
  1869. * @param {object} details
  1870. */
  1871. this.delete = function (details) {
  1872. return new Promise((resolve) => {
  1873. let requestDetails = getRequestDefails("delete", resolve, details);
  1874. delete requestDetails.onprogress;
  1875. requestDetails = handleRequestDetails(requestDetails);
  1876. request(requestDetails);
  1877. });
  1878. };
  1879.  
  1880. /**
  1881. * PUT请求
  1882. * @param {object} details
  1883. * @returns {Promise}
  1884. */
  1885. this.put = function (details) {
  1886. return new Promise((resolve) => {
  1887. let requestDetails = getRequestDefails("put", resolve, details);
  1888. requestDetails = handleRequestDetails(requestDetails);
  1889. request(requestDetails);
  1890. });
  1891. };
  1892.  
  1893. /**
  1894. * 修改默认配置
  1895. * @param {Object} details
  1896. */
  1897. this.config = function (details) {
  1898. defaultDetails = Utils.assign(defaultDetails, details);
  1899. };
  1900. };
  1901.  
  1902. /**
  1903. * 浏览器端的indexedDB操作封装
  1904. * @example
  1905. let db = new Utils.indexedDB('web_DB', 'nav_text')
  1906. let data = {name:'管理员', roleId: 1, type: 1};
  1907. db.save('list',data).then((resolve)=>{
  1908. console.log(resolve,'存储成功')
  1909. })
  1910.  
  1911. db.get('list').then((resolve)=>{
  1912. console.log(resolve,'查询成功')
  1913. })
  1914.  
  1915. db.getPaging('list',20,10).then((resolve)=>{
  1916. console.log(resolve,'查询分页偏移第20,一共10行成功');
  1917. })
  1918.  
  1919. db.delete('list').then(resolve=>{
  1920. console.log(resolve,'删除成功---->>>>>>name')
  1921. })
  1922.  
  1923. db.deleteAll().then(resolve=>{
  1924. console.log(resolve,'清除数据库---->>>>>>name')
  1925. })
  1926. * @param {string} dbName 数据存储名
  1927. * @param {string} storeName 表名
  1928. * @param {number} dbVersion indexDB的版本号
  1929. **/
  1930. Utils.indexedDB = function (
  1931. dbName = "default_db",
  1932. storeName = "default_form",
  1933. dbVersion = 1
  1934. ) {
  1935. this.dbName = dbName;
  1936. this.slqVersion =
  1937. "1"; /* websql的版本号,由于ios的问题,版本号的写法不一样 */
  1938. this.dbVersion = dbVersion;
  1939. this.storeName = storeName;
  1940. this.indexedDB =
  1941. window.indexedDB ||
  1942. window.mozIndexedDB ||
  1943. window.webkitIndexedDB ||
  1944. window.msIndexedDB; /* 监听IndexDB */
  1945. if (!this.indexedDB) {
  1946. alert("很抱歉,您的浏览器不支持indexedDB");
  1947. }
  1948. this.db = {}; /* 缓存数据库,避免同一个页面重复创建和销毁 */
  1949. this.store = null;
  1950. this.errorCode = {
  1951. /* 错误码 */
  1952. success: {
  1953. code: 200,
  1954. msg: "操作成功",
  1955. },
  1956. error: {
  1957. code: 401,
  1958. msg: "操作失败",
  1959. },
  1960. open: { code: 91001, msg: "打开数据库失败" },
  1961. save: { code: 91002, msg: "保存数据失败" },
  1962. get: { code: 91003, msg: "获取数据失败" },
  1963. delete: { code: 91004, msg: "删除数据失败" },
  1964. deleteAll: { code: 91005, msg: "清空数据库失败" },
  1965. };
  1966. let that = this;
  1967. /**
  1968. * 创建 “表”
  1969. * @param {String} dbName 表名
  1970. * @returns
  1971. */
  1972. this.createStore = function (dbName) {
  1973. let txn, store;
  1974. if (that.indexedDB) {
  1975. /* 如果是支持IndexDB的 */
  1976. txn = that.db[dbName].transaction(
  1977. that.storeName,
  1978. "readwrite"
  1979. ); /* IndexDB的读写权限 */
  1980. store = txn.objectStore(that.storeName);
  1981. }
  1982. return store;
  1983. };
  1984. /**
  1985. * 打开数据库
  1986. * @param {function} callback 回调
  1987. * @param {string} dbName 数据库名
  1988. */
  1989. this.open = function (callback, dbName) {
  1990. /* 打开数据库 */
  1991. if (that.indexedDB) {
  1992. /* 如果支持IndexDB */
  1993. if (!that.db[dbName]) {
  1994. /* 如果缓存中没有,则进行数据库的创建或打开,提高效率 */
  1995. let request = that.indexedDB.open(dbName, that.dbVersion);
  1996. request.onerror = function (e) {
  1997. callback({
  1998. code: that.errorCode.open.code,
  1999. msg: that.errorCode.open.msg,
  2000. error: e,
  2001. });
  2002. };
  2003. request.onsuccess = function (e) {
  2004. if (!that.db[dbName]) {
  2005. that.db[dbName] = e.target.result;
  2006. }
  2007. let store = that.createStore(dbName);
  2008. callback(store);
  2009. };
  2010. request.onupgradeneeded = function (e) {
  2011. that.db[dbName] = e.target.result;
  2012. let store = that.db[dbName].createObjectStore(that.storeName, {
  2013. keyPath: "key",
  2014. });
  2015. store.transaction.oncomplete = function (event) {
  2016. callback(store);
  2017. };
  2018. };
  2019. } else {
  2020. /* 如果缓存中已经打开了数据库,就直接使用 */
  2021. let store = that.createStore(dbName);
  2022. callback(store);
  2023. }
  2024. }
  2025. };
  2026. /**
  2027. * 保存数据到数据库
  2028. * @param {any} key 数据key
  2029. * @param {any} value 数据值
  2030. * @returns
  2031. */
  2032. this.save = function (key, value) {
  2033. if (that.indexedDB) {
  2034. return new Promise((resolve, reject) => {
  2035. let dbName = that.dbName;
  2036. let inData = {
  2037. key: key,
  2038. value: value,
  2039. };
  2040. that.open(function (result) {
  2041. let error = result.hasOwnProperty("error");
  2042. if (error) {
  2043. resolve(result);
  2044. } else {
  2045. let request = result.put(inData);
  2046. request.onsuccess = function (e) {
  2047. resolve({
  2048. code: that.errorCode.success.code,
  2049. msg: that.errorCode.success.msg,
  2050. success: true,
  2051. }); /* 保存成功有success 字段 */
  2052. };
  2053. request.onerror = function (e) {
  2054. resolve({
  2055. code: that.errorCode.save.code,
  2056. msg: that.errorCode.save.msg,
  2057. error: e,
  2058. });
  2059. };
  2060. }
  2061. }, dbName);
  2062. });
  2063. }
  2064. };
  2065. /**
  2066. * 根据key获取值
  2067. * @param {string} key 数据key
  2068. * @returns {Promise}
  2069. */
  2070. this.get = function (key) {
  2071. return new Promise((resolve, reject) => {
  2072. let dbName = that.dbName;
  2073. if (that.indexedDB) {
  2074. that.open(function (result) {
  2075. let error =
  2076. result.hasOwnProperty(
  2077. "error"
  2078. ); /* 判断返回的数据中是否有error字段 */
  2079. if (error) {
  2080. reject({
  2081. code: that.errorCode.open.get,
  2082. msg: that.errorCode.get.msg,
  2083. error: error,
  2084. result: result,
  2085. });
  2086. } else {
  2087. let request = result.get(key);
  2088. request.onsuccess = function (e) {
  2089. let result = e.target.result;
  2090. let data = result ? result.value : undefined;
  2091. resolve({
  2092. code: data
  2093. ? that.errorCode.success.code
  2094. : that.errorCode.error.code,
  2095. msg: data
  2096. ? that.errorCode.success.msg
  2097. : that.errorCode.error.msg,
  2098. data: data || [],
  2099. success: true,
  2100. });
  2101. };
  2102. request.onerror = function (e) {
  2103. reject({
  2104. code: that.errorCode.get.code,
  2105. msg: that.errorCode.get.msg,
  2106. result: result,
  2107. error: e,
  2108. });
  2109. };
  2110. }
  2111. }, dbName);
  2112. }
  2113. });
  2114. };
  2115. /**
  2116. * 正则获取数据
  2117. * @param {string} key 数据键
  2118. * @returns
  2119. */
  2120. this.regexpGet = function (key) {
  2121. let list = [];
  2122. return new Promise((resolve, reject) => {
  2123. /* 正则查询 */
  2124. let dbName = that.dbName;
  2125. if (that.indexedDB) {
  2126. that.open(function (result) {
  2127. let error =
  2128. result.hasOwnProperty(
  2129. "error"
  2130. ); /* 判断返回的数据中是否有error字段 */
  2131. if (error) {
  2132. reject({
  2133. code: that.errorCode.open.get,
  2134. msg: that.errorCode.get.msg,
  2135. error: error,
  2136. result: result,
  2137. });
  2138. } else {
  2139. let request = result.getAll();
  2140. request.onsuccess = function (e) {
  2141. let result = e.target.result;
  2142. if (result.length !== 0) {
  2143. result.forEach((item, index) => {
  2144. if (item["key"].match(key)) {
  2145. let concatList = item["value"];
  2146. concatList["key"] = item["key"];
  2147. list = [...list, concatList];
  2148. }
  2149. });
  2150. }
  2151. resolve({
  2152. code: that.errorCode.success.code,
  2153. msg: that.errorCode.success.msg,
  2154. data: list,
  2155. success: true,
  2156. });
  2157. };
  2158. request.onerror = function (e) {
  2159. reject({
  2160. code: that.errorCode.get.code,
  2161. msg: that.errorCode.get.msg,
  2162. result: result,
  2163. error: e,
  2164. });
  2165. };
  2166. }
  2167. }, dbName);
  2168. }
  2169. });
  2170. };
  2171. /**
  2172. * 删除数据
  2173. * @param {string} key 数据键
  2174. * @returns
  2175. */
  2176. this.delete = function (key) {
  2177. return new Promise((resolve, reject) => {
  2178. /* 根据key删除某条数据 */
  2179. let dbName = that.dbName;
  2180. if (that.indexedDB) {
  2181. that.open(function (result) {
  2182. let error = result.hasOwnProperty("error");
  2183. if (error) {
  2184. resolve(result);
  2185. } else {
  2186. let request = result.get(key);
  2187. request.onsuccess = function (e) {
  2188. let recode = e.target.result;
  2189. if (recode) {
  2190. request = result.delete(key);
  2191. }
  2192. resolve({
  2193. code: recode
  2194. ? that.errorCode.success.code
  2195. : that.errorCode.error.code,
  2196. msg: recode
  2197. ? that.errorCode.success.msg
  2198. : that.errorCode.error.msg,
  2199. success: true,
  2200. });
  2201. };
  2202. request.onerror = function (e) {
  2203. resolve({
  2204. code: that.errorCode.delete.code,
  2205. msg: that.errorCode.delete.msg,
  2206. error: e,
  2207. });
  2208. };
  2209. }
  2210. }, dbName);
  2211. }
  2212. });
  2213. };
  2214. /**
  2215. * 删除所有数据
  2216. * @returns
  2217. */
  2218. this.deleteAll = function () {
  2219. return new Promise((resolve, reject) => {
  2220. /* 清空数据库 */
  2221. let dbName = that.dbName;
  2222. if (that.indexedDB) {
  2223. that.open(function (result) {
  2224. let error = result.hasOwnProperty("error");
  2225. if (error) {
  2226. resolve({
  2227. code: that.errorCode.deleteAll.code,
  2228. msg: that.errorCode.deleteAll.msg,
  2229. error: error,
  2230. result: result,
  2231. });
  2232. } else {
  2233. result.clear();
  2234. resolve({
  2235. code: that.errorCode.success.code,
  2236. msg: that.errorCode.success.msg,
  2237. success: true,
  2238. });
  2239. }
  2240. }, dbName);
  2241. }
  2242. });
  2243. };
  2244. };
  2245.  
  2246. /**
  2247. * 判断函数是否是Native
  2248. * @param {function} func
  2249. * @returns {boolean}
  2250. * + true 是Native
  2251. * + false 不是Native
  2252. * @example
  2253. * Utils.isNativeFunc(window.location.assign)
  2254. * > true
  2255. */
  2256. Utils.isNativeFunc = function (func) {
  2257. return Boolean(
  2258. func.toString().match(/^function .*\(\) { \[native code\] }$/)
  2259. );
  2260. };
  2261.  
  2262. /**
  2263. * 判断当前的位置是否位于页面底部附近
  2264. * @param {number} nearValue 判断在页面底部的误差值,默认:50
  2265. * @returns {boolean}
  2266. * + true 在底部附近
  2267. * + false 不在底部附近
  2268. */
  2269. Utils.isNearBottom = function (nearValue = 50) {
  2270. var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  2271. var windowHeight =
  2272. window.innerHeight || document.documentElement.clientHeight;
  2273. var documentHeight = document.documentElement.scrollHeight;
  2274. return scrollTop + windowHeight >= documentHeight - nearValue;
  2275. };
  2276.  
  2277. /**
  2278. * 判断对象是否是元素
  2279. * @param {any} obj
  2280. * @returns {boolean}
  2281. * + true 是元素
  2282. * + false 不是元素
  2283. * @example
  2284. * Utils.isDOM(document.querySelector("a"))
  2285. * > true
  2286. */
  2287. Utils.isDOM = function (obj) {
  2288. return obj instanceof HTMLElement || obj instanceof Node;
  2289. };
  2290.  
  2291. /**
  2292. * 判断对象是否是jQuery对象
  2293. * @param {any} obj
  2294. * @returns {boolean}
  2295. * + true 是jQuery对象
  2296. * + false 不是jQuery对象
  2297. * @example
  2298. * Utils.isJQuery($("a"))
  2299. * > true
  2300. */
  2301. Utils.isJQuery = function (obj) {
  2302. let result = false;
  2303. if (typeof jQuery === "object" && obj instanceof jQuery) {
  2304. result = true;
  2305. }
  2306. if (typeof obj === "object") {
  2307. /* 也有种可能,这个jQuery对象是1.8.3版本的,页面中的jQuery是3.4.1版本的 */
  2308. let jQueryProps = [
  2309. "add",
  2310. "addBack",
  2311. "addClass",
  2312. "after",
  2313. "ajaxComplete",
  2314. "ajaxError",
  2315. "ajaxSend",
  2316. "ajaxStart",
  2317. "ajaxStop",
  2318. "ajaxSuccess",
  2319. "animate",
  2320. "append",
  2321. "appendTo",
  2322. "attr",
  2323. "before",
  2324. "bind",
  2325. "blur",
  2326. "change",
  2327. "children",
  2328. "clearQueue",
  2329. "click",
  2330. "clone",
  2331. "closest",
  2332. "constructor",
  2333. "contents",
  2334. "contextmenu",
  2335. "css",
  2336. "data",
  2337. "dblclick",
  2338. "delay",
  2339. "delegate",
  2340. "dequeue",
  2341. "each",
  2342. "empty",
  2343. "end",
  2344. "eq",
  2345. "extend",
  2346. "fadeIn",
  2347. "fadeOut",
  2348. "fadeTo",
  2349. "fadeToggle",
  2350. "filter",
  2351. "find",
  2352. "first",
  2353. "focus",
  2354. "focusin",
  2355. "focusout",
  2356. "get",
  2357. "has",
  2358. "hasClass",
  2359. "height",
  2360. "hide",
  2361. "hover",
  2362. "html",
  2363. "index",
  2364. "init",
  2365. "innerHeight",
  2366. "innerWidth",
  2367. "insertAfter",
  2368. "insertBefore",
  2369. "is",
  2370. "jquery",
  2371. "keydown",
  2372. "keypress",
  2373. "keyup",
  2374. "last",
  2375. "load",
  2376. "map",
  2377. "mousedown",
  2378. "mouseenter",
  2379. "mouseleave",
  2380. "mousemove",
  2381. "mouseout",
  2382. "mouseover",
  2383. "mouseup",
  2384. "next",
  2385. "nextAll",
  2386. "not",
  2387. "off",
  2388. "offset",
  2389. "offsetParent",
  2390. "on",
  2391. "one",
  2392. "outerHeight",
  2393. "outerWidth",
  2394. "parent",
  2395. "parents",
  2396. "position",
  2397. "prepend",
  2398. "prependTo",
  2399. "prev",
  2400. "prevAll",
  2401. "prevUntil",
  2402. "promise",
  2403. "prop",
  2404. "pushStack",
  2405. "queue",
  2406. "ready",
  2407. "remove",
  2408. "removeAttr",
  2409. "removeClass",
  2410. "removeData",
  2411. "removeProp",
  2412. "replaceAll",
  2413. "replaceWith",
  2414. "resize",
  2415. "scroll",
  2416. "scrollLeft",
  2417. "scrollTop",
  2418. "select",
  2419. "show",
  2420. "siblings",
  2421. "slice",
  2422. "slideDown",
  2423. "slideToggle",
  2424. "slideUp",
  2425. "sort",
  2426. "splice",
  2427. "text",
  2428. "toArray",
  2429. "toggle",
  2430. "toggleClass",
  2431. "trigger",
  2432. "triggerHandler",
  2433. "unbind",
  2434. "width",
  2435. "wrap",
  2436. ];
  2437. for (const jQueryPropsName of jQueryProps) {
  2438. if (!(jQueryPropsName in obj)) {
  2439. result = false;
  2440. /* console.log(jQueryPropsName); */
  2441. break;
  2442. } else {
  2443. result = true;
  2444. }
  2445. }
  2446. }
  2447. return result;
  2448. };
  2449.  
  2450. /**
  2451. * 判断当前设备是否是移动端
  2452. * @return {boolean}
  2453. * + true 是移动端
  2454. * + false 不是移动端
  2455. * @example
  2456. * Utils.isPhone();
  2457. * > true
  2458. **/
  2459. Utils.isPhone = function () {
  2460. return Boolean(
  2461. /(iPhone|iPad|iPod|iOS|Android|Mobile)/i.test(navigator.userAgent)
  2462. );
  2463. };
  2464.  
  2465. /**
  2466. * 判断对象是否不为空
  2467. * @param {any} obj
  2468. * @returns {boolean}
  2469. * + true 不为空
  2470. * + false 为空
  2471. * @example
  2472. * Utils.isNotNull("123");
  2473. * > true
  2474. */
  2475. Utils.isNotNull = function () {
  2476. return !Utils.isNull.apply(this, arguments);
  2477. };
  2478.  
  2479. /**
  2480. * 判断对象或数据是否为空
  2481. * String类型,如 ""、"null"、"undefined"、" "
  2482. * Number类型,如 0
  2483. * Object类型,如 {}
  2484. * @param {arguments} obj 需要判断的变量
  2485. * @returns {boolean}
  2486. * + true 为空
  2487. * + false 不为空
  2488. * @example
  2489. Utils.isNull({});
  2490. > true
  2491. * @example
  2492. Utils.isNull([]);
  2493. > true
  2494. * @example
  2495. Utils.isNull(" ");
  2496. > true
  2497. * @example
  2498. Utils.isNull(function(){});
  2499. > true
  2500. * @example
  2501. Utils.isNull(()=>{}));
  2502. > true
  2503. * @example
  2504. Utils.isNull("undefined");
  2505. > true
  2506. * @example
  2507. Utils.isNull("null");
  2508. > true
  2509. * @example
  2510. Utils.isNull(" ", false);
  2511. > true
  2512. * @example
  2513. Utils.isNull([1],[]);
  2514. > false
  2515. * @example
  2516. Utils.isNull([],[1]);
  2517. > false
  2518. * @example
  2519. Utils.isNull(false,[123]);
  2520. > false
  2521. **/
  2522. Utils.isNull = function () {
  2523. let result = true;
  2524. let checkList = [...arguments];
  2525. for (const objItem of checkList) {
  2526. let itemResult = false;
  2527. switch (typeof objItem) {
  2528. case "undefined":
  2529. case "null":
  2530. itemResult = true;
  2531. break;
  2532. case "object":
  2533. /* object类型的也可能是null */
  2534. if (objItem == null) {
  2535. itemResult = true;
  2536. } else if (
  2537. Array.isArray(objItem) ||
  2538. objItem instanceof NodeList ||
  2539. objItem instanceof FileList
  2540. ) {
  2541. itemResult = objItem.length === 0;
  2542. } else if (objItem instanceof Map || objItem instanceof Set) {
  2543. itemResult = objItem.size === 0;
  2544. } else {
  2545. itemResult = Object.keys(objItem).length === 0;
  2546. }
  2547. break;
  2548. case "number":
  2549. itemResult = objItem === 0;
  2550. break;
  2551. case "string":
  2552. itemResult =
  2553. objItem.trim() === "" ||
  2554. objItem === "null" ||
  2555. objItem === "undefined";
  2556. break;
  2557. case "boolean":
  2558. itemResult = !objItem;
  2559. break;
  2560. case "function":
  2561. let funcStr = objItem.toString().replace(/\s/g, "");
  2562. /* 排除()=>{}、(xxx="")=>{}、function(){}、function(xxx=""){}、 */
  2563. itemResult = Boolean(
  2564. funcStr.match(/^\(.*?\)=>\{\}$|^function.*?\(.*?\)\{\}$/)
  2565. );
  2566. break;
  2567. }
  2568. result = result && itemResult;
  2569. }
  2570.  
  2571. return result;
  2572. };
  2573.  
  2574. /**
  2575. * 判断元素是否在页面中可见
  2576. * @param {[...HTMLElement]|NodeList} dom 需要检查的元素,可以是普通元素|数组形式的元素|通过querySelectorAll获取的元素数组
  2577. * @param {boolean} inView
  2578. * + true 在窗口可视区域
  2579. * + false 不在窗口可视区域
  2580. * @returns {boolean}
  2581. * + true 可见
  2582. * + false 不可见
  2583. * @example
  2584. * Utils.isVisible(document.documentElement)
  2585. * > true
  2586. */
  2587. Utils.isVisible = function (dom, inView = false) {
  2588. let needCheckDomList = [];
  2589. if (dom instanceof Array || dom instanceof NodeList) {
  2590. needCheckDomList = [...dom];
  2591. } else {
  2592. needCheckDomList = [dom];
  2593. }
  2594. let result = true;
  2595. for (const domItem of needCheckDomList) {
  2596. let domDisplay = window.getComputedStyle(domItem);
  2597. if (domDisplay.display === "none") {
  2598. result = false;
  2599. } else {
  2600. let domClientRect = domItem.getBoundingClientRect();
  2601. if (inView) {
  2602. let viewportWidth =
  2603. window.innerWidth || document.documentElement.clientWidth;
  2604. let viewportHeight =
  2605. window.innerHeight || document.documentElement.clientHeight;
  2606. result = !(
  2607. domClientRect.right < 0 ||
  2608. domClientRect.left > viewportWidth ||
  2609. domClientRect.bottom < 0 ||
  2610. domClientRect.top > viewportHeight
  2611. );
  2612. } else {
  2613. result = !(
  2614. domClientRect.bottom === 0 &&
  2615. domClientRect.height === 0 &&
  2616. domClientRect.left === 0 &&
  2617. domClientRect.right === 0 &&
  2618. domClientRect.top === 0 &&
  2619. domClientRect.width === 0 &&
  2620. domClientRect.x === 0 &&
  2621. domClientRect.y === 0
  2622. );
  2623. }
  2624. }
  2625. if (!result) {
  2626. /* 有一个不可见就退出循环 */
  2627. break;
  2628. }
  2629. }
  2630. return result;
  2631. };
  2632.  
  2633. /**
  2634. * 判断是否是Via浏览器环境
  2635. * @returns {boolean}
  2636. * + true 是Via
  2637. * + false 不是Via
  2638. * @example
  2639. * Utils.isWebView_Via()
  2640. * > false
  2641. */
  2642. Utils.isWebView_Via = function () {
  2643. let result = true;
  2644. if (typeof top.window.via === "object") {
  2645. for (const key in Object.values(top.window.via)) {
  2646. if (Object.hasOwnProperty.call(top.window.via, key)) {
  2647. let objValueFunc = top.window.via[key];
  2648. if (
  2649. typeof objValueFunc === "function" &&
  2650. Utils.isNativeFunc(objValueFunc)
  2651. ) {
  2652. result = true;
  2653. } else {
  2654. result = false;
  2655. break;
  2656. }
  2657. }
  2658. }
  2659. } else {
  2660. result = false;
  2661. }
  2662. return result;
  2663. };
  2664.  
  2665. /**
  2666. * 判断是否是X浏览器环境
  2667. * @returns {boolean}
  2668. * + true 是X浏览器
  2669. * + false 不是X浏览器
  2670. * @example
  2671. * Utils.isWebView_X()
  2672. * > false
  2673. */
  2674. Utils.isWebView_X = function () {
  2675. let result = true;
  2676. if (typeof top.window.mbrowser === "object") {
  2677. for (const key in Object.values(top.window.mbrowser)) {
  2678. if (Object.hasOwnProperty.call(top.window.mbrowser, key)) {
  2679. let objValueFunc = top.window.mbrowser[key];
  2680. if (
  2681. typeof objValueFunc === "function" &&
  2682. Utils.isNativeFunc(objValueFunc)
  2683. ) {
  2684. result = true;
  2685. } else {
  2686. result = false;
  2687. break;
  2688. }
  2689. }
  2690. }
  2691. } else {
  2692. result = false;
  2693. }
  2694. return result;
  2695. };
  2696.  
  2697. /**
  2698. * 把Object内的value值全部取出成Array
  2699. * @param {object} obj JSON数据
  2700. * @return {object} 返回数组
  2701. * @example
  2702. * Utils.parseObjectToArray({"工具类":"jsonToArray","return","Array"});
  2703. * @return ['jsonToArray', 'Array']
  2704. **/
  2705. Utils.parseObjectToArray = function (obj) {
  2706. if (typeof obj !== "object") {
  2707. throw new Error("Utils.parseObjectToArray 参数 obj 必须为 object 类型");
  2708. }
  2709. let result = [];
  2710. Object.keys(obj).forEach(function (keyName) {
  2711. result = result.concat(obj[keyName]);
  2712. });
  2713. return result;
  2714. };
  2715.  
  2716. /**
  2717. * 监听某个元素键盘按键事件或window全局按键事件
  2718. * @param {Window|Node|Element} listenObj 需要监听的对象,可以是全局Window或者某个元素
  2719. * @param {function|undefined} callback 自己定义的回调事件,参数1为当前的key,参数2为组合按键,数组类型,包含ctrl、shift、alt和meta(win键或mac的cmd键)
  2720. * @example
  2721. Utils.listenKeyPress(window,(keyName,otherKey,event)=>{
  2722. if(keyName === "Enter"){
  2723. console.log("回车按键")
  2724. }
  2725. if(otherKey.indexOf("ctrl") && keyName === "Enter" ){
  2726. console.log("Ctrl和回车键");
  2727. }
  2728. })
  2729. * @example
  2730. 字母和数字键的键码值(keyCode)
  2731. 按键 键码 按键 键码 按键 键码 按键 键码
  2732. A 65 J 74 S 83 1 49
  2733. B 66 K 75 T 84 2 50
  2734. C 67 L 76 U 85 3 51
  2735. D 68 M 77 V 86 4 52
  2736. E 69 N 78 W 87 5 53
  2737. F 70 O 79 X 88 6 54
  2738. G 71 P 80 Y 89 7 55
  2739. H 72 Q 81 Z 90 8 56
  2740. I 73 R 82 0 48 9 57
  2741.  
  2742. 数字键盘上的键的键码值(keyCode)
  2743. 功能键键码值(keyCode)
  2744. 按键 键码 按键 键码 按键 键码 按键 键码
  2745. 0 96 8 104 F1 112 F7 118
  2746. 1 97 9 105 F2 113 F8 119
  2747. 2 98 * 106 F3 114 F9 120
  2748. 3 99 + 107 F4 115 F10 121
  2749. 4 100 Enter 108 F5 116 F11 122
  2750. 5 101 - 109 F6 117 F12 123
  2751. 6 102 . 110
  2752. 7 103 / 111
  2753. 控制键键码值(keyCode)
  2754. 按键 键码 按键 键码 按键 键码 按键 键码
  2755. BackSpace 8 Esc 27 → 39 -_ 189
  2756. Tab 9 Spacebar 32 ↓ 40 .> 190
  2757. Clear 12 Page Up 33 Insert 45 /? 191
  2758. Enter 13 Page Down 34 Delete 46 `~ 192
  2759. Shift 16 End 35 Num Lock 144 [{ 219
  2760. Control 17 Home 36 ;: 186 \| 220
  2761. Alt 18 ← 37 =+ 187 ]} 221
  2762. Cape Lock 20 ↑ 38 ,< 188 '" 222
  2763.  
  2764. 多媒体键码值(keyCode)
  2765. 按键 键码
  2766. 音量加 175
  2767. 音量减 174
  2768. 停止 179
  2769. 静音 173
  2770. 浏览器 172
  2771. 邮件 180
  2772. 搜索 170
  2773. 收藏 171
  2774. **/
  2775. Utils.listenKeyPress = function (listenObj, callback) {
  2776. if (!(listenObj instanceof Window) && !Utils.isDOM(listenObj)) {
  2777. throw new Error(
  2778. "Utils.listenKeyPress 参数 listenObj 必须为 Window|Node 类型"
  2779. );
  2780. }
  2781. listenObj.addEventListener("keypress", function (event) {
  2782. let keyName = event.code || event.key;
  2783. let otherCodeList = [];
  2784. if (event.ctrlKey) {
  2785. otherCodeList = [...otherCodeList, "ctrl"];
  2786. }
  2787. if (event.altKey) {
  2788. otherCodeList = [...otherCodeList, "alt"];
  2789. }
  2790. if (event.metaKey) {
  2791. otherCodeList = [...otherCodeList, "meta"];
  2792. }
  2793. if (event.shiftKey) {
  2794. otherCodeList = [...otherCodeList, "shift"];
  2795. }
  2796. if (typeof callback === "function") {
  2797. callback(keyName, otherCodeList, event);
  2798. }
  2799. });
  2800. };
  2801.  
  2802. /**
  2803. * 自动锁对象,用于循环判断运行的函数,在循环外new后使用,注意,如果函数内部存在异步操作,需要使用await
  2804. * @param {function|string} func 需要执行的函数
  2805. * @param {function|undefined} scope 函数作用域
  2806. * @param {number} unLockDelayTime 延迟xx毫秒后解锁,默认0
  2807. * @example
  2808. let lock = new Utils.LockFunction(()=>{console.log(1)}))
  2809. lock.run();
  2810. > 1
  2811. * @example
  2812. let lock = new Utils.LockFunction(()=>{console.log(1)}),true) -- 异步操作
  2813. await lock.run();
  2814. > 1
  2815. **/
  2816. Utils.LockFunction = function (func, scope, unLockDelayTime = 0) {
  2817. let flag = false;
  2818. let that = this;
  2819. scope = scope || this;
  2820. /**
  2821. * 锁
  2822. */
  2823. this.lock = function () {
  2824. flag = true;
  2825. };
  2826. /**
  2827. * 解锁
  2828. */
  2829. this.unlock = function () {
  2830. setTimeout(() => {
  2831. flag = false;
  2832. }, unLockDelayTime);
  2833. };
  2834. /**
  2835. * 执行
  2836. * @param {...any} funArgs 参数
  2837. * @returns {Promise}
  2838. */
  2839. this.run = async function (...funArgs) {
  2840. if (flag) {
  2841. return;
  2842. }
  2843. that.lock();
  2844. await func.apply(scope, funArgs); /* arguments调用 */
  2845. that.unlock();
  2846. };
  2847. };
  2848.  
  2849. /**
  2850. * 日志对象
  2851. * @param {function} _GM_info_ 油猴管理器的API GM_info
  2852. * @example
  2853. let log = new Utils.Log(GM_info);
  2854. log.info("普通输出");
  2855. > 普通输出
  2856.  
  2857. log.success("成功输出");
  2858. > 成功输出
  2859.  
  2860. log.error("错误输出");
  2861. > 错误输出
  2862.  
  2863. log.tag = "自定义tag信息";
  2864. log.info("自定义info的颜色","#e0e0e0");
  2865. > 自定义info的颜色
  2866.  
  2867. log.config({
  2868. successColor: "#31dc02",
  2869. errorColor: "#e02d2d",
  2870. infoColor: "black",
  2871. })
  2872. log.success("颜色为#31dc02");
  2873. > 颜色为#31dc02
  2874. */
  2875. Utils.Log = function (_GM_info_) {
  2876. if (typeof _GM_info_ !== "object") {
  2877. throw new Error(
  2878. 'Utils.Log 请添加@grant GM_info且传入该参数,如果使用"use strict"; 就无法获取caller'
  2879. );
  2880. }
  2881. let msgColorDetails = [
  2882. "font-weight: bold; color: cornflowerblue",
  2883. "font-weight: bold; color: cornflowerblue",
  2884. "font-weight: bold; color: darkorange",
  2885. "font-weight: bold; color: cornflowerblue",
  2886. ];
  2887. let details = {
  2888. tag: true /* 输出Tag信息 */,
  2889. successColor: "blue" /* 成功颜色 */,
  2890. errorColor: "red" /* 错误颜色 */,
  2891. infoColor: "0" /* 信息颜色 */,
  2892. debug: false /* 开启debug模式,会在控制台输出调用者位置 */,
  2893. autoClearConsole: false /* 当console输出超过logMaxCount数量自动清理控制台 */,
  2894. logMaxCount: 999 /* console输出的最高数量,autoClearConsole开启则生效 */,
  2895. };
  2896. let logCount = 0;
  2897. /**
  2898. * 解析Error的堆栈获取调用者所在的函数位置
  2899. * @param {list} stack
  2900. */
  2901. let parseErrorStack = function (stack) {
  2902. let result = {
  2903. functionName: "",
  2904. functionPosition: "",
  2905. };
  2906. for (let i = 0; i < stack.length; i++) {
  2907. let stackString = stack[i].trim();
  2908. let stackFunctionName = stackString.match(/^at[\s]+(.+?)[\s]+/i);
  2909. let stackFunctionNamePosition = stackString.match(
  2910. /^at[\s]+.+[\s]+\((.+?)\)/i
  2911. );
  2912. if (stackFunctionName == null) {
  2913. continue;
  2914. }
  2915. stackFunctionName = stackFunctionName[stackFunctionName.length - 1];
  2916. stackFunctionNamePosition =
  2917. stackFunctionNamePosition[stackFunctionNamePosition.length - 1];
  2918. if (
  2919. stackFunctionName === "" ||
  2920. stackFunctionName.match(
  2921. new RegExp(
  2922. "(^Utils.Log.|.<anonymous>$|^Function.each|^NodeList.forEach|^k.fn.init.each)",
  2923. "g"
  2924. )
  2925. )
  2926. ) {
  2927. continue;
  2928. } else {
  2929. result.functionName = stackFunctionName;
  2930. result.functionPosition = stackFunctionNamePosition;
  2931. break;
  2932. }
  2933. }
  2934. return result;
  2935. };
  2936. /* 待恢复的函数或对象 */
  2937. let recoveryList = [];
  2938. /**
  2939. * 检测清理控制台
  2940. * @this {this}
  2941. */
  2942. let checkClearConsole = function () {
  2943. logCount++;
  2944. if (details.autoClearConsole && logCount > details.logMaxCount) {
  2945. console.clear();
  2946. logCount = 0;
  2947. }
  2948. };
  2949. this.tag = _GM_info_?.script?.name || "GM_info缺失";
  2950. /**
  2951. * 控制台-普通输出
  2952. * @param {any} msg
  2953. * @param {String} color
  2954. * @param {String} type
  2955. */
  2956. this.info = function (msg, color, type = "info") {
  2957. checkClearConsole.apply(this);
  2958. let stack = new Error().stack.split("\n");
  2959. if (type === "info") {
  2960. color = color || details.infoColor;
  2961. }
  2962. stack.splice(0, 1);
  2963. let errorStackParse = parseErrorStack(stack);
  2964. let stackFunctionName = errorStackParse["functionName"];
  2965. let stackFunctionNamePosition = errorStackParse["functionPosition"];
  2966. let callerName = stackFunctionName;
  2967. if (typeof msg === "object") {
  2968. /* 要输出的内容是个对象 */
  2969. if (details.tag) {
  2970. if (Array.isArray(msg) && msg.length < 5) {
  2971. console.log(
  2972. `%c[${this.tag}%c-%c${callerName}%c]%c `,
  2973. ...msgColorDetails,
  2974. `color: ${color}`,
  2975. ...msg
  2976. );
  2977. } else {
  2978. console.log(
  2979. `%c[${this.tag}%c-%c${callerName}%c]%c `,
  2980. ...msgColorDetails,
  2981. `color: ${color}`,
  2982. msg
  2983. );
  2984. }
  2985. } else {
  2986. if (Array.isArray(msg) && msg.length < 5) {
  2987. console.log(...msg);
  2988. } else {
  2989. console.log(msg);
  2990. }
  2991. }
  2992. } else {
  2993. if (details.tag) {
  2994. console.log(
  2995. `%c[${this.tag}%c-%c${callerName}%c]%c ${msg}`,
  2996. ...msgColorDetails,
  2997. `color: ${color}`
  2998. );
  2999. } else {
  3000. console.log(`%c${msg}`, `color: ${color}`);
  3001. }
  3002. }
  3003. if (details.debug) {
  3004. console.log(stackFunctionNamePosition);
  3005. }
  3006. };
  3007. /**
  3008. * 控制台-错误输出
  3009. * @param {any} msg
  3010. * @param {string} color
  3011. */
  3012. this.error = function (msg, color) {
  3013. this.info(msg, color || details.errorColor, "error");
  3014. };
  3015. /**
  3016. * 控制台-成功输出
  3017. * @param {any} msg
  3018. * @param {string} color
  3019. */
  3020. this.success = function (msg, color) {
  3021. this.info(msg, color || details.successColor, "success");
  3022. };
  3023. /**
  3024. * 控制台-输出表格
  3025. * @param {object} msgObj
  3026. * @example
  3027. * log.table([{"名字":"example","值":"123"},{"名字":"example2","值":"345"}])
  3028. */
  3029. this.table = function (msgObj) {
  3030. checkClearConsole.apply(this);
  3031. let stack = new Error().stack.split("\n");
  3032. if (type === "info") {
  3033. color = color || details.infoColor;
  3034. }
  3035. stack.splice(0, 1);
  3036. let errorStackParse = parseErrorStack(stack);
  3037. let stackFunctionName = errorStackParse["functionName"];
  3038. let stackFunctionNamePosition = errorStackParse["functionPosition"];
  3039. let callerName = stackFunctionName;
  3040. console.log(
  3041. `%c[${this.tag}%c-%c${callerName}%c]%c`,
  3042. ...msgColorDetails,
  3043. `color: ${color}`
  3044. );
  3045. console.table(msgObj);
  3046. if (details.debug) {
  3047. console.log(stackFunctionNamePosition);
  3048. }
  3049. };
  3050. /**
  3051. * 配置Log对象的颜色
  3052. * @param {object} paramDetails 配置信息
  3053. */
  3054. this.config = function (paramDetails) {
  3055. details = Object.assign(details, paramDetails);
  3056. };
  3057. /**
  3058. * 禁用输出
  3059. */
  3060. this.disable = function () {
  3061. let that = this;
  3062. Object.keys(this)
  3063. .filter((keyName) => Boolean(keyName.match(/info|error|success|table/)))
  3064. .forEach((keyName) => {
  3065. let value = {};
  3066. value[keyName] = that[keyName];
  3067. recoveryList = [...recoveryList, value];
  3068. that[keyName] = () => {};
  3069. });
  3070. };
  3071. /**
  3072. * 恢复输出
  3073. */
  3074. this.recovery = function () {
  3075. let that = this;
  3076. recoveryList.forEach((item) => {
  3077. let keyName = Object.keys(item);
  3078. that[keyName] = item[keyName];
  3079. });
  3080. recoveryList = [];
  3081. };
  3082. };
  3083.  
  3084. /**
  3085. * 合并数组内的JSON的值字符串
  3086. * @param {[...any]} data 需要合并的数组
  3087. * @param {function|string|undefined} handleFunc 处理的函数|JSON的key
  3088. * @returns {string}
  3089. * @example
  3090. * Utils.mergeArrayToString([{"name":"数组内数据部分字段合并成字符串->"},{"name":"mergeToString"}],(item)=>{return item["name"]});
  3091. * > '数组内数据部分字段合并成字符串->mergeToString'
  3092. **/
  3093. Utils.mergeArrayToString = function (data, handleFunc) {
  3094. if (!(data instanceof Array)) {
  3095. throw new Error("Utils.mergeArrayToString 参数 data 必须为 Array 类型");
  3096. }
  3097. let content = "";
  3098. if (typeof handleFunc === "function") {
  3099. data.forEach((item) => {
  3100. content += handleFunc(item);
  3101. });
  3102. } else if (typeof handleFunc === "string") {
  3103. data.forEach((item) => {
  3104. content += item[handleFunc];
  3105. });
  3106. } else {
  3107. data.forEach((item) => {
  3108. Object.values(item)
  3109. .filter((item2) => typeof item2 === "string")
  3110. .forEach((item3) => {
  3111. content += item3;
  3112. });
  3113. });
  3114. }
  3115. return content;
  3116. };
  3117.  
  3118. /**
  3119. * 监听页面元素改变并处理
  3120. * @param {object|Node|HTMLElement} target 需要监听的元素,如果不存在,可以等待它出现
  3121. * @param {object} observer_config MutationObserver的配置
  3122. * @example
  3123. Utils.mutationObserver(document.querySelector("div.xxxx"),{
  3124. "callback":(mutations, observer)=>{},
  3125. "config":{childList:true,attributes:true}
  3126. });
  3127. * @example
  3128. Utils.mutationObserver(document.querySelectorAll("div.xxxx"),{
  3129. "callback":(mutations, observer)=>{},
  3130. "config":{childList:true,attributes:true}}
  3131. );
  3132. * @example
  3133. Utils.mutationObserver($("div.xxxx"),{
  3134. "callback":(mutations, observer)=>{},
  3135. "config":{childList:true,attributes:true}}
  3136. );
  3137. **/
  3138. Utils.mutationObserver = function (target, observer_config) {
  3139. if (
  3140. !(target instanceof Node) &&
  3141. !(target instanceof NodeList) &&
  3142. !Utils.isJQuery(target)
  3143. ) {
  3144. throw new Error(
  3145. "Utils.mutationObserver 参数 target 必须为 Node|NodeList|jQuery类型"
  3146. );
  3147. }
  3148.  
  3149. let default_obverser_config = {
  3150. /* 监听到元素有反馈,需执行的函数 */
  3151. callback: () => {},
  3152. config: {
  3153. /**
  3154. * @type {boolean|undefined}
  3155. * + true 监听以 target 为根节点的整个子树。包括子树中所有节点的属性,而不仅仅是针对 target
  3156. * + false (默认) 不生效
  3157. */
  3158. subtree: undefined,
  3159. /**
  3160. * @type {boolean|undefined}
  3161. * + true 监听 target 节点中发生的节点的新增与删除(同时,如果 subtree 为 true,会针对整个子树生效)
  3162. * + false (默认) 不生效
  3163. */
  3164. childList: undefined,
  3165. /**
  3166. * @type {boolean|undefined}
  3167. * + true 观察所有监听的节点属性值的变化。默认值为 true,当声明了 attributeFilter 或 attributeOldValue
  3168. * + false (默认) 不生效
  3169. */
  3170. attributes: undefined,
  3171. /**
  3172. * 一个用于声明哪些属性名会被监听的数组。如果不声明该属性,所有属性的变化都将触发通知
  3173. * @type {[...string]|undefined}
  3174. */
  3175. attributeFilter: undefined,
  3176. /**
  3177. * @type {boolean|undefined}
  3178. * + true 记录上一次被监听的节点的属性变化;可查阅 MutationObserver 中的 Monitoring attribute values 了解关于观察属性变化和属性值记录的详情
  3179. * + false (默认) 不生效
  3180. */
  3181. attributeOldValue: undefined,
  3182. /**
  3183. * @type {boolean|undefined}
  3184. * + true 监听声明的 target 节点上所有字符的变化。默认值为 true,如果声明了 characterDataOldValue
  3185. * + false (默认) 不生效
  3186. */
  3187. characterData: undefined,
  3188. /**
  3189. * @type {boolean|undefined}
  3190. * + true 记录前一个被监听的节点中发生的文本变化
  3191. * + false (默认) 不生效
  3192. */
  3193. characterDataOldValue: undefined,
  3194. },
  3195. };
  3196. observer_config = Utils.assign(default_obverser_config, observer_config);
  3197. let MutationObserver =
  3198. window.MutationObserver ||
  3199. window.webkitMutationObserver ||
  3200. window.MozMutationObserver;
  3201. let mutationObserver = new MutationObserver(function (mutations, observer) {
  3202. observer_config?.callback(mutations, observer);
  3203. });
  3204. if (target instanceof Node) {
  3205. /* 传入的参数是节点元素 */
  3206. mutationObserver.observe(target, observer_config.config);
  3207. } else if (target instanceof NodeList) {
  3208. /* 传入的参数是节点元素数组 */
  3209. target.forEach((item) => {
  3210. mutationObserver.observe(item, observer_config.config);
  3211. });
  3212. } else if (Utils.isJQuery(target)) {
  3213. /* 传入的参数是jQuery对象 */
  3214. target.each((index, item) => {
  3215. mutationObserver.observe(item, observer_config.config);
  3216. });
  3217. } else {
  3218. /* 未知 */
  3219. console.error("Utils.mutationObserver 未知参数", arguments);
  3220. }
  3221. return mutationObserver;
  3222. };
  3223.  
  3224. /**
  3225. * 去除全局window下的Utils,返回控制权
  3226. * @returns {Utils}
  3227. * @example
  3228. * let utils = Utils.noConflict();
  3229. * > ...
  3230. */
  3231. Utils.noConflict = function () {
  3232. delete window[GLOBAL_NAME_SPACE];
  3233. if (originalUtils) {
  3234. window[GLOBAL_NAME_SPACE] = originalUtils;
  3235. }
  3236. return Utils;
  3237. };
  3238.  
  3239. /**
  3240. * 恢复/释放该对象内的为function,让它无效/有效
  3241. * @param {object} needReleaseObject 需要操作的对象
  3242. * @param {string} needReleaseName 需要操作的对象的名字
  3243. * @param {array} functionNameList 需要释放的方法,如果为空,默认全部方法
  3244. * @param {boolean} release
  3245. * + true (默认) 释放该对象下的某些方法
  3246. * + false 恢复该对象下的某些方法
  3247. * @example
  3248. // 释放该方法
  3249. Utils.noConflictFunc(console,"console",["log"],true);
  3250. console.log;
  3251. > () => {}
  3252.  
  3253. * @example
  3254. // 恢复该方法
  3255. Utils.noConflictFunc(console,"console",["log"],false);
  3256. console.log;
  3257. > ƒ log() { [native code] }
  3258.  
  3259. * @example
  3260. // 释放所有方法
  3261. Utils.noConflictFunc(console,"console",[],true);
  3262. console.debug;
  3263. > () => {}
  3264.  
  3265. * @example
  3266. // 恢复所有方法
  3267. Utils.noConflictFunc(console,"console",[],false);
  3268. console.debug;
  3269. > ƒ log() { [native code] }
  3270. **/
  3271. Utils.noConflictFunc = function (
  3272. needReleaseObject,
  3273. needReleaseName,
  3274. functionNameList = [],
  3275. release = true
  3276. ) {
  3277. if (typeof needReleaseObject !== "object") {
  3278. throw new Error(
  3279. "Utils.noConflictFunc 参数 needReleaseObject 必须为 object 类型"
  3280. );
  3281. }
  3282. if (typeof needReleaseName !== "string") {
  3283. throw new Error(
  3284. "Utils.noConflictFunc 参数 needReleaseName 必须为 string 类型"
  3285. );
  3286. }
  3287. if (!Array.isArray(functionNameList)) {
  3288. throw new Error(
  3289. "Utils.noConflictFunc 参数 functionNameList 必须为 Array 类型"
  3290. );
  3291. }
  3292. let needReleaseKey = "__" + needReleaseName;
  3293. /**
  3294. * 复制对象
  3295. * @param {object} obj
  3296. * @returns {object}
  3297. */
  3298. function cloneObj(obj) {
  3299. let newObj = {};
  3300. if (obj instanceof Array) {
  3301. newObj = [];
  3302. }
  3303. for (let key in obj) {
  3304. let val = obj[key];
  3305. newObj[key] = typeof val === "object" ? cloneObj(val) : val;
  3306. }
  3307. return newObj;
  3308. }
  3309. /**
  3310. * 释放所有
  3311. */
  3312. function releaseAll() {
  3313. if (typeof window[needReleaseKey] !== "undefined") {
  3314. /* 已存在 */
  3315. return;
  3316. }
  3317. window[needReleaseKey] = cloneObj(needReleaseObject);
  3318. Object.values(needReleaseObject).forEach((value) => {
  3319. if (typeof value === "function") {
  3320. needReleaseObject[value.name] = () => {};
  3321. }
  3322. });
  3323. }
  3324. /**
  3325. * 释放单个
  3326. */
  3327. function releaseOne() {
  3328. Array.from(functionNameList).forEach((item) => {
  3329. Object.values(needReleaseObject).forEach((value) => {
  3330. if (typeof value === "function") {
  3331. if (typeof window[needReleaseKey] === "undefined") {
  3332. window[needReleaseKey] = {};
  3333. }
  3334. if (item === value.name) {
  3335. window[needReleaseKey][value.name] =
  3336. needReleaseObject[value.name];
  3337. needReleaseObject[value.name] = () => {};
  3338. }
  3339. }
  3340. });
  3341. });
  3342. }
  3343. /**
  3344. * 恢复所有
  3345. */
  3346. function recoveryAll() {
  3347. if (typeof window[needReleaseKey] === "undefined") {
  3348. /* 未存在 */
  3349. return;
  3350. }
  3351. Object.assign(needReleaseObject, window[needReleaseKey]);
  3352. delete window[needReleaseKey];
  3353. }
  3354.  
  3355. /**
  3356. * 恢复单个
  3357. */
  3358. function recoveryOne() {
  3359. if (typeof window[needReleaseKey] === "undefined") {
  3360. /* 未存在 */
  3361. return;
  3362. }
  3363. Array.from(functionNameList).forEach((item) => {
  3364. if (window[needReleaseKey][item]) {
  3365. needReleaseObject[item] = window[needReleaseKey][item];
  3366. delete window[needReleaseKey][item];
  3367. if (Object.keys(window[needReleaseKey]).length === 0) {
  3368. delete window[needReleaseKey];
  3369. }
  3370. }
  3371. });
  3372. }
  3373. if (release) {
  3374. /* 释放 */
  3375. if (functionNameList.length === 0) {
  3376. releaseAll();
  3377. } else {
  3378. /* 对单个进行操作 */
  3379. releaseOne();
  3380. }
  3381. } else {
  3382. /* 恢复 */
  3383. if (functionNameList.length === 0) {
  3384. recoveryAll();
  3385. } else {
  3386. /* 对单个进行操作 */
  3387. recoveryOne();
  3388. }
  3389. }
  3390. };
  3391. /**
  3392. * base64转blob
  3393. * @param {string} dataUri base64的数据
  3394. * @return {string} blob的链接
  3395. * @example
  3396. * Utils.parseBase64ToBlob("data:image/jpeg;base64,.....");
  3397. * > blob://xxxxxxx
  3398. **/
  3399. Utils.parseBase64ToBlob = function (dataUri) {
  3400. if (typeof dataUri !== "string") {
  3401. throw new Error(
  3402. "Utils.parseBase64ToBlob 参数 dataUri 必须为 string 类型"
  3403. );
  3404. }
  3405. let dataUriSplit = dataUri.split(","),
  3406. dataUriMime = dataUriSplit[0].match(/:(.*?);/)[1],
  3407. dataUriBase64Str = atob(dataUriSplit[1]),
  3408. dataUriLength = dataUriBase64Str.length,
  3409. u8arr = new Uint8Array(dataUriLength);
  3410. while (dataUriLength--) {
  3411. u8arr[dataUriLength] = dataUriBase64Str.charCodeAt(dataUriLength);
  3412. }
  3413. return new Blob([u8arr], {
  3414. type: dataUriMime,
  3415. });
  3416. };
  3417.  
  3418. /**
  3419. * base64转File对象
  3420. * @param {string} dataUri base64的数据
  3421. * @return {string} blob的链接
  3422. * @example
  3423. * Utils.parseBase64ToFile("data:image/jpeg;base64,.....","测试文件");
  3424. * > object
  3425. **/
  3426. Utils.parseBase64ToFile = function (dataUri, fileName) {
  3427. if (typeof dataUri !== "string") {
  3428. throw new Error(
  3429. "Utils.parseBase64ToFile 参数 dataUri 必须为 string 类型"
  3430. );
  3431. }
  3432. if (typeof fileName !== "string") {
  3433. throw new Error(
  3434. "Utils.parseBase64ToFile 参数 fileName 必须为 string 类型"
  3435. );
  3436. }
  3437. let dataUriSplit = dataUri.split(","),
  3438. dataUriMime = dataUriSplit[0].match(/:(.*?);/)[1],
  3439. dataUriBase64Str = atob(dataUriSplit[1]),
  3440. dataUriLength = dataUriBase64Str.length,
  3441. u8arr = new Uint8Array(dataUriLength);
  3442. while (dataUriLength--) {
  3443. u8arr[dataUriLength] = dataUriBase64Str.charCodeAt(dataUriLength);
  3444. }
  3445. return new File([u8arr], fileName, {
  3446. type: dataUriMime,
  3447. });
  3448. };
  3449.  
  3450. /**
  3451. * 将正则匹配到的结果取出最后一个值并转换成int格式
  3452. * @param {[...any]} matchList 正则匹配的列表
  3453. * @param {number|string} defaultValue 正则匹配的列表为空时,或者正则匹配的列表最后一项不为Int,返回该默认值
  3454. * @example
  3455. * Utils.parseInt(["dadaadada123124","123124"],0);
  3456. * > 123124
  3457. *
  3458. * @example
  3459. * Utils.parseInt(null,0);
  3460. * > 0
  3461. * @example
  3462. * Utils.parseInt(["aaaaaa"]);
  3463. * > 0
  3464. *
  3465. * @example
  3466. * Utils.parseInt(["aaaaaa"],"66");
  3467. * > 66
  3468. *
  3469. * @example
  3470. * Utils.parseInt(["aaaaaaa"],"aa");
  3471. * > NaN
  3472. **/
  3473. Utils.parseInt = function (matchList = [], defaultValue = 0) {
  3474. if (matchList == null) {
  3475. return parseInt(defaultValue);
  3476. }
  3477. let parseValue = parseInt(matchList[matchList.length - 1]);
  3478. if (isNaN(parseValue)) {
  3479. parseValue = parseInt(defaultValue);
  3480. }
  3481. return parseValue;
  3482. };
  3483.  
  3484. /**
  3485. * blob转File对象
  3486. * @param {string} blobUrl 需要转换的blob的链接
  3487. * @param {string} fileName 转换成的File对象的文件名称
  3488. * @return {object} File对象
  3489. * @example
  3490. * Utils.blobToFile("blob://xxxxx");
  3491. * > object
  3492. **/
  3493. Utils.parseBlobToFile = function (blobUrl, fileName) {
  3494. const file = new File([blob], fileName, { type: blob.type });
  3495. return new Promise((resolve, reject) => {
  3496. fetch(blobUrl)
  3497. .then((response) => response.blob())
  3498. .then((blob) => {
  3499. const file = blobToFile(blob, "example.txt");
  3500. resolve(file);
  3501. })
  3502. .catch((error) => {
  3503. console.error("Error:", error);
  3504. reject(error);
  3505. });
  3506. });
  3507. };
  3508.  
  3509. /**
  3510. * 解析CDATA格式的内容字符串
  3511. * @param {string} [text=""] 传入CDATA字符串
  3512. * @returns {string} 返回解析出的内容
  3513. * @example
  3514. * let xml = "<root><![CDATA[This is some CDATA content.]]></root>";
  3515. * console.log(Utils.parseCDATA(xml));
  3516. * > This is some CDATA content.
  3517. */
  3518. Utils.parseCDATA = function (text = "") {
  3519. let result = "";
  3520. let cdataRegexp = /<\!\[CDATA\[([\s\S]*)\]\]>/;
  3521. let cdataMatch = cdataRegexp.exec(text.trim());
  3522. if (cdataMatch && cdataMatch.length > 1) {
  3523. result = cdataMatch[cdataMatch.length - 1];
  3524. }
  3525. return result;
  3526. };
  3527.  
  3528. /**
  3529. * 【异步函数】File对象转base64
  3530. * @param {object} fileObj 需要转换的File对象
  3531. * @return {string} base64格式的数据
  3532. * @example
  3533. * await Utils.parseFileToBase64(object);
  3534. * > 'data:image/jpeg:base64/,xxxxxx'
  3535. **/
  3536. Utils.parseFileToBase64 = async function (fileObj) {
  3537. let reader = new FileReader();
  3538. reader.readAsDataURL(fileObj);
  3539. return new Promise((resolve) => {
  3540. reader.onload = function (event) {
  3541. resolve(event.target.result);
  3542. };
  3543. });
  3544. };
  3545.  
  3546. /**
  3547. * 解析字符串
  3548. * @param {string} text 要解析的 DOMString。它必须包含 HTML、xml、xhtml+xml 或 svg 文档。
  3549. * @param {string} mimeType 解析成的类型,包括:text/html、text/xml、application/xml、application/xhtml+xml、image/svg+xml
  3550. * @returns {HTMLElement|XMLDocument|SVGElement}
  3551. * @example
  3552. * Utils.parseFromString("<p>123<p>");
  3553. * > #document
  3554. */
  3555. Utils.parseFromString = function (text, mimeType = "text/html") {
  3556. let parser = new DOMParser();
  3557. return parser.parseFromString(text, mimeType);
  3558. };
  3559.  
  3560. /**
  3561. * 阻止事件传递
  3562. * @param {HTMLElement} ele 要进行处理的元素
  3563. * @param {string|[...string]} eventNameList 要阻止的事件名|列表
  3564. * @param {Event|undefined} paramEvent 事件
  3565. * @example
  3566. * Utils.preventEvent(document.querySelector("a"),"click")
  3567. * @example
  3568. * Utils.preventEvent(event);
  3569. */
  3570. Utils.preventEvent = function (ele, eventNameList = []) {
  3571. function stopEvent(event) {
  3572. /* 阻止事件的默认行为发生。例如,当点击一个链接时,浏览器会默认打开链接的URL */
  3573. event?.preventDefault();
  3574. /* 停止事件的传播,阻止它继续向更上层的元素冒泡,事件将不会再传播给其他的元素 */
  3575. event?.stopPropagation();
  3576. /* 阻止事件传播,并且还能阻止元素上的其他事件处理程序被触发 */
  3577. event?.stopImmediatePropagation();
  3578. return false;
  3579. }
  3580. if (arguments.length === 1) {
  3581. return stopEvent(arguments[0]);
  3582. } else if (arguments.length === 2) {
  3583. if (typeof eventNameList === "string") {
  3584. eventNameList = [eventNameList];
  3585. }
  3586. eventNameList.forEach((eventName) => {
  3587. ele.addEventListener(eventName, function (event) {
  3588. return stopEvent(event);
  3589. });
  3590. });
  3591. }
  3592. };
  3593.  
  3594. /**
  3595. * 在canvas元素节点上绘制进度圆圈
  3596. * @param {object} paramConfig 配置信息
  3597. * @example
  3598. let progress = new Utils.Process({canvasNode:document.querySelector("canvas")});
  3599. progress.draw();
  3600. * **/
  3601. Utils.Progress = function (paramConfig) {
  3602. this.config = {
  3603. canvasNode: null /* canvas元素节点 */,
  3604. deg: 95 /* 绘制角度 */,
  3605. progress: 0 /* 进度 */,
  3606. lineWidth: 10 /* 绘制的线宽度 */,
  3607. lineBgColor: "#1e637c" /* 绘制的背景颜色 */,
  3608. lineColor: "#25deff",
  3609. textColor: "#000000" /* 文字的颜色 */,
  3610. fontSize: 22 /* 字体大小(px) */,
  3611. circleRadius: 50 /* 圆半径 */,
  3612. draw: () => {} /* 控制绘制 */,
  3613. };
  3614. this.config = Utils.assign(this.config, paramConfig);
  3615. if (!(this.config.canvasNode instanceof HTMLCanvasElement)) {
  3616. throw new Error(
  3617. "Utils.Progress 参数 canvasNode 必须是 HTMLCanvasElement"
  3618. );
  3619. }
  3620. let ctx = this.config.canvasNode.getContext("2d"); /* 获取画笔 */
  3621. let width = this.config.canvasNode.width; /* 元素宽度 */
  3622. let height = this.config.canvasNode.height; /* 元素高度 */
  3623.  
  3624. /* 清除锯齿 */
  3625. if (window.devicePixelRatio) {
  3626. this.config.canvasNode.style.width = width + "px";
  3627. this.config.canvasNode.style.height = height + "px";
  3628. this.config.canvasNode.height = height * window.devicePixelRatio;
  3629. this.config.canvasNode.width = width * window.devicePixelRatio;
  3630. ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
  3631. }
  3632. /* 设置线宽 */
  3633. ctx.lineWidth = this.config.lineWidth;
  3634. /* 绘制 */
  3635. this.draw = function () {
  3636. let degActive = (this.config.progress * 360) / 100;
  3637. ctx.clearRect(0, 0, width, height); //清除画布
  3638. ctx.beginPath(); //开始绘制底圆
  3639. ctx.arc(width / 2, height / 2, this.config.circleRadius, 1, 8);
  3640. ctx.strokeStyle = this.config.lineBgColor;
  3641. ctx.stroke();
  3642. ctx.beginPath(); //开始绘制动态圆
  3643. ctx.arc(
  3644. width / 2,
  3645. height / 2,
  3646. this.config.circleRadius,
  3647. -Math.PI / 2,
  3648. (degActive * Math.PI) / 180 - Math.PI / 2
  3649. );
  3650. ctx.strokeStyle = this.config.lineColor;
  3651. ctx.stroke();
  3652. let txt = parseInt(this.config.progress) + "%"; //获取百分比
  3653. ctx.font = this.config.fontSize + "px SimHei";
  3654. let w = ctx.measureText(txt).width; //获取文本宽度
  3655. let h = this.config.fontSize / 2;
  3656. ctx.fillStyle = this.config.textColor;
  3657. ctx.fillText(txt, width / 2 - w / 2, height / 2 + h / 2);
  3658. }.bind(this);
  3659. };
  3660.  
  3661. /**
  3662. * 劫持使isTrust为true的真实点击事件,注入时刻,ducument-start
  3663. * @example
  3664. * Utils.registerTrustClickEvent()
  3665. */
  3666. Utils.registerTrustClickEvent = function () {
  3667. function trustEvent(event) {
  3668. return new Proxy(event, {
  3669. get: function (target, property) {
  3670. if (property === "isTrusted") {
  3671. return true;
  3672. } else {
  3673. return Reflect.get(target, property);
  3674. }
  3675. },
  3676. });
  3677. }
  3678. const originalListener = EventTarget.prototype.addEventListener;
  3679. EventTarget.prototype.addEventListener = function () {
  3680. if (this === document && arguments[0] === "click") {
  3681. const fn = arguments[1];
  3682. arguments[1] = function (e) {
  3683. fn.call(this, trustEvent(e));
  3684. };
  3685. }
  3686. return originalListener.apply(this, arguments);
  3687. };
  3688. };
  3689.  
  3690. /**
  3691. * 复制到剪贴板
  3692. * @param {string|number} text - 需要复制到剪贴板的文本
  3693. * @example
  3694. * Utils.setClip("xxxx");
  3695. **/
  3696. Utils.setClip = function (text) {
  3697. if (typeof text !== "string" && typeof text !== "number") {
  3698. console.error(typeof text);
  3699. throw new Error("复制的貌似不是string或number类型");
  3700. }
  3701. // 获取剪贴板对象
  3702. const clipboard = navigator.clipboard;
  3703.  
  3704. // 复制文本到剪贴板
  3705. clipboard
  3706. .writeText(text)
  3707. .then(() => {
  3708. console.log("复制成功");
  3709. })
  3710. .catch((err) => {
  3711. console.error("复制失败,使用第二种方式", err);
  3712. let chipBoardNode = document.createElement("input");
  3713. chipBoardNode.type = "text";
  3714. chipBoardNode.setAttribute("style", "opacity:0;position:absolute;");
  3715. chipBoardNode.id = "whitesevClipBoardInput";
  3716. document.body.append(chipBoardNode);
  3717. let clipBoardInputNode = document.querySelector(
  3718. "#whitesevClipBoardInput"
  3719. );
  3720. clipBoardInputNode.value = text;
  3721. clipBoardInputNode.removeAttribute("disabled");
  3722. clipBoardInputNode.select();
  3723. document.execCommand("copy");
  3724. clipBoardInputNode.remove();
  3725. });
  3726. };
  3727.  
  3728. /**
  3729. * 【异步函数】等待N秒执行函数
  3730. * @param {function|string} func 待执行的函数(字符串)
  3731. * @param {number} delayTime 延时时间(ms)
  3732. * @return {?undefined} 函数的返回值
  3733. * @example
  3734. * await Utils.setTimeout(()=>{}, 2500);
  3735. * > ƒ tryCatchObj() {}
  3736. * @example
  3737. * await Utils.setTimeout("()=>{console.log(12345)}", 2500);
  3738. * > ƒ tryCatchObj() {}
  3739. **/
  3740. Utils.setTimeout = async function (func, delayTime = 0) {
  3741. if (typeof func !== "function" && typeof func !== "string") {
  3742. throw new Error("Utils.setTimeout 参数 func 必须为 function|string 类型");
  3743. }
  3744. if (typeof delayTime !== "number") {
  3745. throw new Error("Utils.setTimeout 参数 delayTime 必须为 number 类型");
  3746. }
  3747. return new Promise((resolve) => {
  3748. setTimeout(() => {
  3749. resolve(Utils.tryCatch().run(func));
  3750. }, delayTime);
  3751. });
  3752. };
  3753.  
  3754. /**
  3755. * 【异步函数】延迟xxx毫秒
  3756. * @param {number} delayTime 延时时间(ms)
  3757. * @example
  3758. * await Utils.sleep(2500)
  3759. **/
  3760. Utils.sleep = async function (delayTime) {
  3761. if (typeof delayTime !== "number") {
  3762. throw new Error("Utils.sleep 参数 delayTime 必须为 number 类型");
  3763. }
  3764. return new Promise((resolve) => {
  3765. setTimeout(() => {
  3766. resolve();
  3767. }, delayTime);
  3768. });
  3769. };
  3770.  
  3771. /**
  3772. * 向右拖动滑块
  3773. * @param {string|Element|Node} selector 选择器|元素
  3774. * @param {number} offsetX 水平拖动长度,默认浏览器宽度
  3775. * @example
  3776. * Utils.dragSlider("#xxxx");
  3777. * @example
  3778. * Utils.dragSlider("#xxxx",100);
  3779. */
  3780. Utils.dragSlider = function (selector, offsetX = window.innerWidth) {
  3781. function initMouseEvent(eventName, offSetX, offSetY) {
  3782. let win = unsafeWindow || window;
  3783. let mouseEvent = document.createEvent("MouseEvents");
  3784. mouseEvent.initMouseEvent(
  3785. eventName,
  3786. true,
  3787. true,
  3788. win,
  3789. 0,
  3790. offSetX,
  3791. offSetY,
  3792. offSetX,
  3793. offSetY,
  3794. false,
  3795. false,
  3796. false,
  3797. false,
  3798. 0,
  3799. null
  3800. );
  3801. return mouseEvent;
  3802. }
  3803. let sliderElement =
  3804. typeof selector === "string"
  3805. ? document.querySelector(selector)
  3806. : selector;
  3807. if (
  3808. !(sliderElement instanceof Node) ||
  3809. !(sliderElement instanceof Element)
  3810. ) {
  3811. throw new Error("Utils.dragSlider 参数selector 必须为Node/Element类型");
  3812. }
  3813. let rect = sliderElement.getBoundingClientRect(),
  3814. x0 = rect.x || rect.left,
  3815. y0 = rect.y || rect.top,
  3816. x1 = x0 + offsetX,
  3817. y1 = y0;
  3818. sliderElement.dispatchEvent(initMouseEvent("mousedown", x0, y0));
  3819. sliderElement.dispatchEvent(initMouseEvent("mousemove", x1, y1));
  3820. sliderElement.dispatchEvent(initMouseEvent("mouseleave", x1, y1));
  3821. sliderElement.dispatchEvent(initMouseEvent("mouseout", x1, y1));
  3822. };
  3823.  
  3824. /**
  3825. * 数组按照内部某个值的大小比对排序,如[{"time":"2022-1-1"},{"time":"2022-2-2"}]
  3826. * @param {[...any]|NodeList|function} data 数据|获取数据的方法
  3827. * @param {string|function} getPropertyValueFunc 数组内部项的某个属性的值的方法,参数为这个项
  3828. * @param {boolean} sortByDesc 排序方式,默认true倒序(值最大排第一个,如:6、5、4、3...),false为升序(值最小排第一个,如:1、2、3、4...)
  3829. * @return {object} 返回比较排序完成的数组
  3830. * @example
  3831. * Utils.sortListByProperty([{"time":"2022-1-1"},{"time":"2022-2-2"}],(item)=>{return item["time"]})
  3832. * > [{time: '2022-2-2'},{time: '2022-1-1'}]
  3833. * @example
  3834. * Utils.sortListByProperty([{"time":"2022-1-1"},{"time":"2022-2-2"}],(item)=>{return item["time"]},false)
  3835. * > [{time: '2022-1-1'},{time: '2022-2-2'}]
  3836. **/
  3837. Utils.sortListByProperty = function (
  3838. data,
  3839. getPropertyValueFunc,
  3840. sortByDesc = true
  3841. ) {
  3842. if (
  3843. typeof getPropertyValueFunc !== "function" &&
  3844. typeof getPropertyValueFunc !== "string"
  3845. ) {
  3846. throw new Error(
  3847. "Utils.sortListByProperty 参数 getPropertyValueFunc 必须为 function|string 类型"
  3848. );
  3849. }
  3850. if (typeof sortByDesc !== "boolean") {
  3851. throw new Error(
  3852. "Utils.sortListByProperty 参数 sortByDesc 必须为 boolean 类型"
  3853. );
  3854. }
  3855. let getObjValue = function (obj) {
  3856. return typeof getPropertyValueFunc === "string"
  3857. ? obj[getPropertyValueFunc]
  3858. : getPropertyValueFunc(obj);
  3859. };
  3860. /**
  3861. * 排序方法
  3862. * @param {any} after_obj
  3863. * @param {any} before_obj
  3864. * @returns
  3865. */
  3866. let sortFunc = function (after_obj, before_obj) {
  3867. let beforeValue = getObjValue(before_obj); /* 前 */
  3868. let afterValue = getObjValue(after_obj); /* 后 */
  3869. if (sortByDesc) {
  3870. if (afterValue > beforeValue) {
  3871. return -1;
  3872. } else if (afterValue < beforeValue) {
  3873. return 1;
  3874. } else {
  3875. return 0;
  3876. }
  3877. } else {
  3878. if (afterValue < beforeValue) {
  3879. return -1;
  3880. } else if (afterValue > beforeValue) {
  3881. return 1;
  3882. } else {
  3883. return 0;
  3884. }
  3885. }
  3886. };
  3887. /**
  3888. * 排序元素方法
  3889. * @param {NodeList|jQuery} nodeList 元素列表
  3890. * @param {function} getNodeListFunc 获取元素列表的函数
  3891. */
  3892. let sortNodeFunc = function (nodeList, getNodeListFunc) {
  3893. let nodeListLength = nodeList.length;
  3894. for (let i = 0; i < nodeListLength - 1; i++) {
  3895. for (let j = 0; j < nodeListLength - 1 - i; j++) {
  3896. let beforeNode = nodeList[j];
  3897. let afterNode = nodeList[j + 1];
  3898. let beforeValue = getObjValue(beforeNode); /* 前 */
  3899. let afterValue = getObjValue(afterNode); /* 后 */
  3900. if (
  3901. (sortByDesc == true && beforeValue < afterValue) ||
  3902. (sortByDesc == false && beforeValue > afterValue)
  3903. ) {
  3904. /* 升序/降序 */
  3905. /* 相邻元素两两对比 */
  3906. let temp = beforeNode.nextElementSibling;
  3907. afterNode.after(beforeNode);
  3908. if (temp == null) {
  3909. /* 如果为空,那么是最后一个元素,使用append */
  3910. temp.parentNode.appendChild(afterNode);
  3911. } else {
  3912. /* 不为空,使用before */
  3913. temp.before(afterNode);
  3914. }
  3915. nodeList = getNodeListFunc();
  3916. }
  3917. }
  3918. }
  3919. };
  3920. let result = data;
  3921. let getDataFunc = null;
  3922. if (data instanceof Function) {
  3923. getDataFunc = data;
  3924. data = data();
  3925. }
  3926. if (Array.isArray(data)) {
  3927. data.sort(sortFunc);
  3928. } else if (data instanceof NodeList || Utils.isJQuery(data)) {
  3929. sortNodeFunc(data, getDataFunc);
  3930. result = getDataFunc();
  3931. } else {
  3932. throw new Error(
  3933. "Utils.sortListByProperty 参数 data 必须为 Array|NodeList|jQuery 类型"
  3934. );
  3935. }
  3936. return result;
  3937. };
  3938.  
  3939. /**
  3940. * 字符串转Object对象,类似'{"test":""}' => {"test":""}
  3941. * @param {string} data
  3942. * @returns {object}
  3943. * @example
  3944. * Utils.toJSON("{123:123}")
  3945. * > {123:123}
  3946. */
  3947. Utils.toJSON = function (data) {
  3948. let result = {};
  3949. Utils.tryCatch()
  3950. .config({ log: false })
  3951. .error(() => {
  3952. Utils.tryCatch()
  3953. .error(() => {
  3954. result = window.eval("(" + data + ")");
  3955. })
  3956. .run(() => {
  3957. if (
  3958. data &&
  3959. /^[\],:{}\s]*$/.test(
  3960. data
  3961. .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@")
  3962. .replace(
  3963. /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
  3964. "]"
  3965. )
  3966. .replace(/(?:^|:|,)(?:\s*\[)+/g, "")
  3967. )
  3968. ) {
  3969. result = new Function("return " + data)();
  3970. }
  3971. });
  3972. })
  3973. .run(() => {
  3974. data = data.trim();
  3975. result = JSON.parse(data);
  3976. });
  3977. return result;
  3978. };
  3979.  
  3980. /**
  3981. * 提供一个封装了 try-catch 的函数,可以执行传入的函数并捕获其可能抛出的错误,并通过传入的错误处理函数进行处理。
  3982. * @return {{run:function,config:function,error:function}} - 返回一个对象,其中包含 error 和 run 两个方法。
  3983. * @example
  3984. * Utils.tryCatch().error().run(()=>{console.log(1)});
  3985. * > 1
  3986. * @example
  3987. * Utils.tryCatch().config({log:true}).error((error)=>{console.log(error)}).run(()=>{throw new Error('测试错误')});
  3988. * > ()=>{throw new Error('测试错误')}出现错误
  3989. */
  3990. Utils.tryCatch = function () {
  3991. // 定义变量和函数
  3992. let func = null;
  3993. let funcThis = null;
  3994. let handleErrorFunc = null;
  3995. let defaultDetails = {
  3996. log: true,
  3997. };
  3998. let funcArgs = arguments;
  3999. /**
  4000. * @function tryCatchObj
  4001. * @description 空函数,用于链式调用。
  4002. */
  4003. function tryCatchObj() {}
  4004.  
  4005. /**
  4006. * 配置
  4007. * @param {object} paramDetails
  4008. */
  4009. tryCatchObj.config = function (paramDetails) {
  4010. defaultDetails = Utils.assign(defaultDetails, paramDetails);
  4011. return tryCatchObj;
  4012. };
  4013. /**
  4014. * 设置错误处理函数。
  4015. * @param {function|string} handler - 错误处理函数,可以是 function 或者 string 类型。如果是 string 类型,则会被当做代码进行执行。
  4016. * @return {function} - 返回 tryCatchObj 函数。
  4017. */
  4018. tryCatchObj.error = function (handler) {
  4019. handleErrorFunc = handler;
  4020. return tryCatchObj;
  4021. };
  4022.  
  4023. /**
  4024. * 执行传入的函数并捕获其可能抛出的错误,并通过传入的错误处理函数进行处理。
  4025. * @param {function|string} fn - 待执行函数,可以是 function 或者 string 类型。如果是 string 类型,则会被当做代码进行执行。
  4026. * @param {object|null} fnThis - 待执行函数的作用域,用于apply指定
  4027. * @return {any|function} - 如果函数有返回值,则返回该返回值;否则返回 tryCatchObj 函数以支持链式调用。
  4028. * @throws {Error} - 如果传入参数不符合要求,则会抛出相应类型的错误。
  4029. */
  4030. tryCatchObj.run = function (fn, fnThis) {
  4031. func = fn;
  4032. funcThis = fnThis;
  4033. let result = executeTryCatch(func, handleErrorFunc, funcThis);
  4034. return result !== undefined ? result : tryCatchObj;
  4035. };
  4036.  
  4037. /**
  4038. * 执行传入的函数并捕获其可能抛出的错误,并通过传入的错误处理函数进行处理。
  4039. * @param {function|string} func - 待执行函数,可以是 function 或者 string 类型。如果是 string 类型,则会被当做代码进行执行。
  4040. * @param {function|string|null} handleErrorFunc - 错误处理函数,可以是 function 或者 string 类型。如果是 string 类型,则会被当做代码进行执行。
  4041. * @param {object|null} funcThis - 待执行函数的作用域,用于apply指定
  4042. * @return {any|undefined} - 如果函数有返回值,则返回该返回值;否则返回 undefined。
  4043. */
  4044. function executeTryCatch(func, handleErrorFunc, funcThis) {
  4045. let result = undefined;
  4046. try {
  4047. if (typeof func === "string") {
  4048. (function () {
  4049. eval(func);
  4050. }).apply(funcThis, funcArgs);
  4051. } else {
  4052. result = func.apply(funcThis, funcArgs);
  4053. }
  4054. } catch (error) {
  4055. if (defaultDetails.log) {
  4056. console.log(
  4057. `%c ${func?.name ? func?.name : func + "出现错误"} `,
  4058. "color: #f20000"
  4059. );
  4060. console.log(`%c 错误原因:${error}`, "color: #f20000");
  4061. console.trace(func);
  4062. }
  4063. if (handleErrorFunc) {
  4064. if (typeof handleErrorFunc === "string") {
  4065. result = function () {
  4066. return eval(handleErrorFunc);
  4067. }.apply(funcThis, [...funcArgs, error]);
  4068. } else {
  4069. result = handleErrorFunc.apply(funcThis, [...funcArgs, error]);
  4070. }
  4071. }
  4072. }
  4073. return result;
  4074. }
  4075.  
  4076. // 返回 tryCatchObj 函数
  4077. return tryCatchObj;
  4078. };
  4079.  
  4080. /**
  4081. * 数组去重,去除重复的值
  4082. * @param {[...any]} uniqueArrayData 需要去重的数组
  4083. * @param {[...any]} compareArrayData 用来比较的数组
  4084. * @param {function} compareFun 数组比较方法,如果值相同,去除该数据
  4085. * @returns {object} 返回去重完毕的数组
  4086. * @example
  4087. * Utils.uniqueArray([1,2,3],[1,2],(item,item2)=>{return item===item2 ? true:false});
  4088. * > [3]
  4089. *
  4090. * @example
  4091. * Utils.uniqueArray([1,2,3],[1,2]);
  4092. * > [3]
  4093. *
  4094. * @example
  4095. * Utils.uniqueArray([{"key":1,"value":2},{"key":2}],[{"key":1}],(item,item2)=>{return item["key"] === item2["key"] ? true:false});
  4096. * > [{"key": 2}]
  4097. **/
  4098. Utils.uniqueArray = function (
  4099. uniqueArrayData = [],
  4100. compareArrayData = [],
  4101. compareFun = (item, item2) => {
  4102. return item === item2;
  4103. }
  4104. ) {
  4105. return Array.from(uniqueArrayData).filter(
  4106. (item) =>
  4107. !Array.from(compareArrayData).some(function (item2) {
  4108. return compareFun(item, item2);
  4109. })
  4110. );
  4111. };
  4112.  
  4113. /**
  4114. * 观察对象的set、get
  4115. * @param {object} obj 观察的对象
  4116. * @param {string} propertyName 观察的对象的属性名
  4117. * @param {function} setCallBack 触发set的回调
  4118. * @param {function} getCallBack 触发get的回调
  4119. * @example
  4120. * Utils.watchObj(window,"test",(value)=>{console.log("test出现,值是",value)},()=>{return 111;});
  4121. *
  4122. * window.test = 1;
  4123. * > test出现,值是 1
  4124. * console.log(window.test);
  4125. * > 111;
  4126. */
  4127. Utils.watchObj = function (obj, propertyName, setCallBack, getCallBack) {
  4128. Object.defineProperty(obj, propertyName, {
  4129. set(value) {
  4130. setCallBack(value);
  4131. },
  4132. get() {
  4133. return getCallBack();
  4134. },
  4135. });
  4136. };
  4137.  
  4138. /**
  4139. * 等待函数数组全部执行完毕,注意,每个函数的顺序不是同步
  4140. * @param {[...any] | [...HTMLElement]} data 需要遍历的数组
  4141. * @param {function} handleFunc 对该数组进行操作的函数,该函数的参数为数组格式的参数,[数组下标,数组项]
  4142. * @example
  4143. * await Utils.waitArrayLoopToEnd([func,func,func],xxxFunction);
  4144. **/
  4145. Utils.waitArrayLoopToEnd = function (data, handleFunc) {
  4146. if (typeof handleFunc !== "function" && typeof handleFunc !== "string") {
  4147. throw new Error(
  4148. "Utils.waitArrayLoopToEnd 参数 handleDataFunction 必须为 function|string 类型"
  4149. );
  4150. }
  4151. return Promise.all(
  4152. Array.from(data).map(async (item, index) => {
  4153. await Utils.tryCatch(index, item).run(handleFunc);
  4154. })
  4155. );
  4156. };
  4157.  
  4158. /**
  4159. * 等待指定节点出现,支持多个 selector
  4160. * @param {...string} nodeSelectors - 一个或多个节点选择器,必须为字符串类型
  4161. * @returns {Promise} 返回一个 Promise 对象,成功时返回节点数组,如[ [...nodes], [...nodes] ]
  4162. * 如果参数 nodeSelectors 只有一个的话,返回 [...nodes]
  4163. * @example
  4164. Utils.waitNode("div.xxx","a.xxx").then( (nodeList)=>{
  4165. let divNodesList = nodeList[0];
  4166. let aNodeList = nodeList[1];
  4167. })
  4168. */
  4169. Utils.waitNode = function (...nodeSelectors) {
  4170. /* 检查每个参数是否为字符串类型 */
  4171. for (const nodeSelector of nodeSelectors) {
  4172. if (typeof nodeSelector !== "string") {
  4173. throw new Error("Utils.waitNode 参数必须为 ...string 类型");
  4174. }
  4175. }
  4176.  
  4177. return new Promise((resolve) => {
  4178. /* 防止触发第二次回调 */
  4179. let isReturn = false;
  4180.  
  4181. /* 检查所有选择器是否匹配到节点 */
  4182. const checkNodes = (observer) => {
  4183. let isFind = true;
  4184. let selectNodes = [];
  4185. for (let i = 0; i < nodeSelectors.length; i++) {
  4186. const selector = nodeSelectors[i];
  4187. const nodeList = document.querySelectorAll(selector);
  4188. selectNodes = selectNodes.concat(nodeList);
  4189. if (nodeList.length === 0) {
  4190. /* 没找到,直接退出循环 */
  4191. isFind = false;
  4192. break;
  4193. }
  4194. }
  4195. if (isFind) {
  4196. isReturn = true;
  4197. observer?.disconnect();
  4198. /* 如果只有一个选择器,那么返回第一个 */
  4199. if (selectNodes.length === 1) {
  4200. resolve(selectNodes[0]);
  4201. } else {
  4202. resolve(selectNodes);
  4203. }
  4204. }
  4205. };
  4206.  
  4207. /* 在函数开始时检查节点是否已经存在 */
  4208. checkNodes();
  4209.  
  4210. /* 监听 DOM 的变化,直到至少有一个节点被匹配到 */
  4211. Utils.mutationObserver(document.documentElement, {
  4212. config: { subtree: true, childList: true, attributes: true },
  4213. callback: (mutations, observer) => {
  4214. if (isReturn) {
  4215. return;
  4216. }
  4217. checkNodes(observer);
  4218. },
  4219. });
  4220. });
  4221. };
  4222.  
  4223. /**
  4224. * 定时检查对象是否存在
  4225. * @param {object} checkObj 检查的对象
  4226. * @param {string} checkPropertyName 检查的对象的属性名
  4227. * @param {Promise}
  4228. * @example
  4229. * await Utils.waitObj(window,"test");
  4230. * console.log("test success set");
  4231. *
  4232. * window.test = 1;
  4233. * > "test success set"
  4234. *
  4235. */
  4236. Utils.waitObj = function (checkObj, checkPropertyName) {
  4237. return new Promise((resolve) => {
  4238. if (checkPropertyName in checkObj) {
  4239. resolve(checkObj[checkPropertyName]);
  4240. return;
  4241. }
  4242. Object.defineProperty(checkObj, checkPropertyName, {
  4243. set: function (value) {
  4244. resolve(value);
  4245. },
  4246. });
  4247. });
  4248. };
  4249.  
  4250. window[GLOBAL_NAME_SPACE] = Utils;
  4251. })();

QingJ © 2025

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