WhiteSevsUtils

一个好用的工具类

当前为 2024-01-07 提交的版本,查看 最新版本

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

  1. /// <reference path="../库.ts/Utils.d.ts" />
  2.  
  3. /**
  4. * 方便好用的工具类
  5. * @copyright GPL-3.0-only
  6. * @author WhiteSev
  7. **/
  8. (function (global, factory) {
  9. /**
  10. * 不使用define
  11. * typeof define === "function" && define.amd
  12. * define(factory)
  13. */
  14. if (typeof exports === "object" && typeof module !== "undefined") {
  15. /* 适用于NodeJs或typeScript */
  16. module.exports = factory();
  17. } else {
  18. global = typeof globalThis !== "undefined" ? globalThis : global || self;
  19. /* 适用于浏览器中,且this对象是window,如果this是其它,那么会在其它对象下注册对象 */
  20. global.Utils = factory(global.Utils);
  21. }
  22. })(typeof window !== "undefined" ? window : this, function (AnotherUtils) {
  23. const Utils = {};
  24. /**
  25. * @type {string} 工具类的版本
  26. */
  27. Utils.version = "2024-1-7";
  28. /**
  29. * JSON数据从源端替换到目标端中,如果目标端存在该数据则替换,不添加,返回结果为目标端替换完毕的结果
  30. * @function
  31. * @param {object} [target={}] 目标端
  32. * @param {object} [source={}] 源端
  33. * @returns {object}
  34. * @example
  35. * Utils.assign({"1":1,"2":{"3":3}}, {"2":{"3":4}});
  36. * > {
  37. "1": 1,
  38. "2": {
  39. "3": 4
  40. }
  41. }
  42. **/
  43. Utils.assign = function (target = {}, source = {}) {
  44. if (Array.isArray(source)) {
  45. let canTraverse = source.filter((item) => {
  46. return typeof item === "object";
  47. });
  48. if (!canTraverse.length) {
  49. return source;
  50. }
  51. }
  52. for (let targetKeyName in target) {
  53. let targetValue = target[targetKeyName];
  54. if (targetKeyName in source) {
  55. let sourceValue = source[targetKeyName];
  56. if (typeof sourceValue === "object" && !Utils.isDOM(sourceValue)) {
  57. /* 源端的值是object类型,且不是元素对象 */
  58. if (Object.keys(sourceValue).length) {
  59. target[targetKeyName] = Utils.assign(targetValue, sourceValue);
  60. } else {
  61. target[targetKeyName] = sourceValue;
  62. }
  63. } else {
  64. /* 直接赋值 */
  65. target[targetKeyName] = sourceValue;
  66. }
  67. }
  68. }
  69. return target;
  70. };
  71.  
  72. /**
  73. * @callback UtilsAjaxHookHook
  74. * @param { XMLHttpRequest } request
  75. */
  76. /**
  77. * @typedef {object} UtilsAjaxHookFilterArray
  78. * @property {?"xhr"|"fetch"} type 可选,应是xhr或fetch
  79. * @property {?string|RegExp} url 可选,字符串或正则表达式,无需完全匹配。
  80. * @property {?string} method 可选,不区分大小写。
  81. * @property {?boolean} async 可选,布尔值。
  82. */
  83. /**
  84. * @callback UtilsAjaxHookFilter
  85. * @param { UtilsAjaxHookFilterArray[] } filterArray
  86. */
  87. /**
  88. * @typedef {object} UtilsAjaxHookResult
  89. * @property { (request: UtilsAjaxHookHook )=>{} } hook 劫持
  90. * @property {UtilsAjaxHookFilter} filter 过滤
  91. * @property {()=>{}} protect 阻止xhr和fetch被改写
  92. * @property {()=>{}} unhook 取消劫持
  93. */
  94. /**
  95. * ajax劫持库,支持xhr和fetch劫持。
  96. * + 来源:https://bbs.tampermonkey.net.cn/thread-3284-1-1.html
  97. * + 作者:cxxjackie
  98. * + 版本:1.3.3
  99. * + 文档:https://scriptcat.org/zh-CN/script-show-page/637/
  100. *
  101. * @returns { UtilsAjaxHookResult }
  102. *
  103. * @example
  104. // 核心方法,通过一个回调函数进行劫持,每次请求发生时自动调用回调函数。可以将所有劫持放在同一回调函数中,也可以多次调用hook方法。
  105. ajaxHooker.hook(request => {
  106. console.log(request);
  107. });
  108. * @example
  109. // response
  110. // 响应内容,必须通过一个回调函数进行读取和修改。响应内容为一个对象
  111. // 包含finalUrl、status、responseHeaders和被读取的响应数据
  112. // 除响应数据可修改,其他属性是只读的。
  113. // 响应数据是哪个属性取决于哪个属性被读取
  114. // xhr可能的属性为response、responseText、responseXML
  115. // fetch可能的属性为arrayBuffer、blob、formData、json、text
  116. // 在控制台输出时,xhr响应将包含所有属性,但只有被读取过的属性具有明确的值
  117. // 修改对应属性即可影响读取结果,进而实现响应数据的修改
  118. ajaxHooker.hook(request => {
  119. if (request.url === 'https://www.example.com/') {
  120. request.response = res => {
  121. console.log(res);
  122. res.responseText += 'test';
  123. };
  124. }
  125. });
  126. @example
  127. // 异步特性无法作用于同步请求,但同步修改仍然有效
  128. // 你可以将以上所有可修改属性赋值为Promise,原请求将被阻塞直至Promise完成(若发生reject,数据将不会被修改)
  129. // 此特性可用于异步劫持。以下是一个异步修改响应数据的例子
  130. ajaxHooker.hook(request => {
  131. request.response = res => {
  132. const responseText = res.responseText; // 注意保存原数据
  133. res.responseText = new Promise(resolve => {
  134. setTimeout(() => {
  135. resolve(responseText + 'test');
  136. }, 3000);
  137. });
  138. };
  139. });
  140.  
  141. // 也可以传入async回调函数以实现异步
  142. ajaxHooker.hook(async request => {
  143. request.data = await modifyData(request.data);
  144. request.response = async res => {
  145. res.responseText = await modifyResponse(res.responseText);
  146. };
  147. });
  148. @example
  149. // 应于hook方法之前执行,此方法若尽早执行,有助于提升性能。
  150. // 为hook方法设置过滤规则,只有符合规则的请求才会触发hook。过滤规则是一个对象数组,参考下例
  151. ajaxHooker.filter([
  152. {type: 'xhr', url: 'www.example.com', method: 'GET', async: true},
  153. {url: /^http/},
  154. ]);
  155. @example
  156. // 如果库劫持失败,可能是其他代码对xhr/fetch进行了二次劫持,protect方法会尝试阻止xhr和fetch被改写。应于document-start阶段尽早执行,部分网页下可能引发错误,谨慎使用。
  157. ajaxHooker.protect();
  158. @example
  159. // 将xhr和fetch恢复至劫持前的状态,调用此方法后,hook方法不再生效。
  160. ajaxHooker.unhook();
  161. */
  162. Utils.ajaxHooker = function () {
  163. "use strict";
  164. const win = window.unsafeWindow || document.defaultView || window;
  165. const toString = Object.prototype.toString;
  166. const getDescriptor = Object.getOwnPropertyDescriptor;
  167. const hookFns = [];
  168. const realXhr = win.XMLHttpRequest;
  169. const realFetch = win.fetch;
  170. const resProto = win.Response.prototype;
  171. const xhrResponses = ["response", "responseText", "responseXML"];
  172. const fetchResponses = ["arrayBuffer", "blob", "formData", "json", "text"];
  173. const fetchInitProps = [
  174. "method",
  175. "headers",
  176. "body",
  177. "mode",
  178. "credentials",
  179. "cache",
  180. "redirect",
  181. "referrer",
  182. "referrerPolicy",
  183. "integrity",
  184. "keepalive",
  185. "signal",
  186. "priority",
  187. ];
  188. const xhrAsyncEvents = ["readystatechange", "load", "loadend"];
  189. let filter;
  190. function emptyFn() {}
  191. function errorFn(err) {
  192. console.error(err);
  193. }
  194. function defineProp(obj, prop, getter, setter) {
  195. Object.defineProperty(obj, prop, {
  196. configurable: true,
  197. enumerable: true,
  198. get: getter,
  199. set: setter,
  200. });
  201. }
  202. function readonly(obj, prop, value = obj[prop]) {
  203. defineProp(obj, prop, () => value, emptyFn);
  204. }
  205. function writable(obj, prop, value = obj[prop]) {
  206. Object.defineProperty(obj, prop, {
  207. configurable: true,
  208. enumerable: true,
  209. writable: true,
  210. value: value,
  211. });
  212. }
  213. function shouldFilter(type, url, method, async) {
  214. return (
  215. filter &&
  216. !filter.find((obj) => {
  217. switch (true) {
  218. case obj.type && obj.type !== type:
  219. case toString.call(obj.url) === "[object String]" &&
  220. !url.includes(obj.url):
  221. case toString.call(obj.url) === "[object RegExp]" &&
  222. !obj.url.test(url):
  223. case obj.method &&
  224. obj.method.toUpperCase() !== method.toUpperCase():
  225. case "async" in obj && obj.async !== async:
  226. return false;
  227. }
  228. return true;
  229. })
  230. );
  231. }
  232. function parseHeaders(obj) {
  233. const headers = {};
  234. switch (toString.call(obj)) {
  235. case "[object String]":
  236. for (const line of obj.trim().split(/[\r\n]+/)) {
  237. const parts = line.split(/\s*:\s*/);
  238. if (parts.length !== 2) continue;
  239. const lheader = parts[0].toLowerCase();
  240. if (lheader in headers) {
  241. headers[lheader] += ", " + parts[1];
  242. } else {
  243. headers[lheader] = parts[1];
  244. }
  245. }
  246. return headers;
  247. case "[object Headers]":
  248. for (const [key, val] of obj) {
  249. headers[key] = val;
  250. }
  251. return headers;
  252. case "[object Object]":
  253. return { ...obj };
  254. default:
  255. return headers;
  256. }
  257. }
  258. class AHRequest {
  259. constructor(request) {
  260. this.request = request;
  261. this.requestClone = { ...this.request };
  262. this.response = {};
  263. }
  264. waitForHookFns() {
  265. return Promise.all(
  266. hookFns.map((fn) => {
  267. try {
  268. return Promise.resolve(fn(this.request)).then(emptyFn, errorFn);
  269. } catch (err) {
  270. console.error(err);
  271. }
  272. })
  273. );
  274. }
  275. waitForResponseFn() {
  276. try {
  277. return Promise.resolve(this.request.response(this.response)).then(
  278. emptyFn,
  279. errorFn
  280. );
  281. } catch (err) {
  282. console.error(err);
  283. return Promise.resolve();
  284. }
  285. }
  286. waitForRequestKeys() {
  287. if (this.reqPromise) return this.reqPromise;
  288. const requestKeys = ["url", "method", "abort", "headers", "data"];
  289. return (this.reqPromise = this.waitForHookFns().then(() =>
  290. Promise.all(
  291. requestKeys.map((key) =>
  292. Promise.resolve(this.request[key]).then(
  293. (val) => (this.request[key] = val),
  294. (e) => (this.request[key] = this.requestClone[key])
  295. )
  296. )
  297. )
  298. ));
  299. }
  300. waitForResponseKeys() {
  301. if (this.resPromise) return this.resPromise;
  302. const responseKeys =
  303. this.request.type === "xhr" ? xhrResponses : fetchResponses;
  304. return (this.resPromise = this.waitForResponseFn().then(() =>
  305. Promise.all(
  306. responseKeys.map((key) => {
  307. const descriptor = getDescriptor(this.response, key);
  308. if (descriptor && "value" in descriptor) {
  309. return Promise.resolve(descriptor.value).then(
  310. (val) => (this.response[key] = val),
  311. (e) => delete this.response[key]
  312. );
  313. } else {
  314. delete this.response[key];
  315. }
  316. })
  317. )
  318. ));
  319. }
  320. }
  321. class XhrEvents {
  322. constructor() {
  323. this.events = {};
  324. }
  325. add(type, event) {
  326. if (type.startsWith("on")) {
  327. this.events[type] = typeof event === "function" ? event : null;
  328. } else {
  329. this.events[type] = this.events[type] || new Set();
  330. this.events[type].add(event);
  331. }
  332. }
  333. remove(type, event) {
  334. if (type.startsWith("on")) {
  335. this.events[type] = null;
  336. } else {
  337. this.events[type] && this.events[type].delete(event);
  338. }
  339. }
  340. _sIP() {
  341. this.ajaxHooker_isStopped = true;
  342. }
  343. trigger(e) {
  344. if (e.ajaxHooker_isTriggered || e.ajaxHooker_isStopped) return;
  345. e.stopImmediatePropagation = this._sIP;
  346. this.events[e.type] &&
  347. this.events[e.type].forEach((fn) => {
  348. !e.ajaxHooker_isStopped && fn.call(e.target, e);
  349. });
  350. this.events["on" + e.type] &&
  351. this.events["on" + e.type].call(e.target, e);
  352. e.ajaxHooker_isTriggered = true;
  353. }
  354. clone() {
  355. const eventsClone = new XhrEvents();
  356. for (const type in this.events) {
  357. if (type.startsWith("on")) {
  358. eventsClone.events[type] = this.events[type];
  359. } else {
  360. eventsClone.events[type] = new Set([...this.events[type]]);
  361. }
  362. }
  363. return eventsClone;
  364. }
  365. }
  366. const xhrMethods = {
  367. readyStateChange(e) {
  368. if (e.target.readyState === 4) {
  369. e.target.dispatchEvent(
  370. new CustomEvent("ajaxHooker_responseReady", { detail: e })
  371. );
  372. } else {
  373. e.target.__ajaxHooker.eventTrigger(e);
  374. }
  375. },
  376. asyncListener(e) {
  377. e.target.__ajaxHooker.eventTrigger(e);
  378. },
  379. setRequestHeader(header, value) {
  380. const ah = this.__ajaxHooker;
  381. ah.originalXhr.setRequestHeader(header, value);
  382. if (this.readyState !== 1) return;
  383. if (header in ah.headers) {
  384. ah.headers[header] += ", " + value;
  385. } else {
  386. ah.headers[header] = value;
  387. }
  388. },
  389. addEventListener(...args) {
  390. const ah = this.__ajaxHooker;
  391. if (xhrAsyncEvents.includes(args[0])) {
  392. ah.proxyEvents.add(args[0], args[1]);
  393. } else {
  394. ah.originalXhr.addEventListener(...args);
  395. }
  396. },
  397. removeEventListener(...args) {
  398. const ah = this.__ajaxHooker;
  399. if (xhrAsyncEvents.includes(args[0])) {
  400. ah.proxyEvents.remove(args[0], args[1]);
  401. } else {
  402. ah.originalXhr.removeEventListener(...args);
  403. }
  404. },
  405. open(method, url, async = true, ...args) {
  406. const ah = this.__ajaxHooker;
  407. ah.url = url.toString();
  408. ah.method = method.toUpperCase();
  409. ah.async = !!async;
  410. ah.openArgs = args;
  411. ah.headers = {};
  412. for (const key of xhrResponses) {
  413. ah.proxyProps[key] = {
  414. get: () => {
  415. const val = ah.originalXhr[key];
  416. ah.originalXhr.dispatchEvent(
  417. new CustomEvent("ajaxHooker_readResponse", {
  418. detail: { key, val },
  419. })
  420. );
  421. return val;
  422. },
  423. };
  424. }
  425. return ah.originalXhr.open(method, url, ...args);
  426. },
  427. sendFactory(realSend) {
  428. return function (data) {
  429. const ah = this.__ajaxHooker;
  430. const xhr = ah.originalXhr;
  431. if (xhr.readyState !== 1) return realSend.call(xhr, data);
  432. ah.eventTrigger = (e) => ah.proxyEvents.trigger(e);
  433. if (shouldFilter("xhr", ah.url, ah.method, ah.async)) {
  434. xhr.addEventListener(
  435. "ajaxHooker_responseReady",
  436. (e) => {
  437. ah.eventTrigger(e.detail);
  438. },
  439. { once: true }
  440. );
  441. return realSend.call(xhr, data);
  442. }
  443. const request = {
  444. type: "xhr",
  445. url: ah.url,
  446. method: ah.method,
  447. abort: false,
  448. headers: ah.headers,
  449. data: data,
  450. response: null,
  451. async: ah.async,
  452. };
  453. if (!ah.async) {
  454. const requestClone = { ...request };
  455. hookFns.forEach((fn) => {
  456. try {
  457. toString.call(fn) === "[object Function]" && fn(request);
  458. } catch (err) {
  459. console.error(err);
  460. }
  461. });
  462. for (const key in request) {
  463. if (toString.call(request[key]) === "[object Promise]") {
  464. request[key] = requestClone[key];
  465. }
  466. }
  467. xhr.open(request.method, request.url, ah.async, ...ah.openArgs);
  468. for (const header in request.headers) {
  469. xhr.setRequestHeader(header, request.headers[header]);
  470. }
  471. data = request.data;
  472. xhr.addEventListener(
  473. "ajaxHooker_responseReady",
  474. (e) => {
  475. ah.eventTrigger(e.detail);
  476. },
  477. { once: true }
  478. );
  479. realSend.call(xhr, data);
  480. if (toString.call(request.response) === "[object Function]") {
  481. const response = {
  482. finalUrl: xhr.responseURL,
  483. status: xhr.status,
  484. responseHeaders: parseHeaders(xhr.getAllResponseHeaders()),
  485. };
  486. for (const key of xhrResponses) {
  487. defineProp(
  488. response,
  489. key,
  490. () => {
  491. return (response[key] = ah.originalXhr[key]);
  492. },
  493. (val) => {
  494. if (toString.call(val) !== "[object Promise]") {
  495. delete response[key];
  496. response[key] = val;
  497. }
  498. }
  499. );
  500. }
  501. try {
  502. request.response(response);
  503. } catch (err) {
  504. console.error(err);
  505. }
  506. for (const key of xhrResponses) {
  507. ah.proxyProps[key] = { get: () => response[key] };
  508. }
  509. }
  510. return;
  511. }
  512. const req = new AHRequest(request);
  513. req.waitForRequestKeys().then(() => {
  514. if (request.abort) return;
  515. xhr.open(request.method, request.url, ...ah.openArgs);
  516. for (const header in request.headers) {
  517. xhr.setRequestHeader(header, request.headers[header]);
  518. }
  519. data = request.data;
  520. xhr.addEventListener(
  521. "ajaxHooker_responseReady",
  522. (e) => {
  523. if (typeof request.response !== "function")
  524. return ah.eventTrigger(e.detail);
  525. req.response = {
  526. finalUrl: xhr.responseURL,
  527. status: xhr.status,
  528. responseHeaders: parseHeaders(xhr.getAllResponseHeaders()),
  529. };
  530. for (const key of xhrResponses) {
  531. defineProp(
  532. req.response,
  533. key,
  534. () => {
  535. return (req.response[key] = ah.originalXhr[key]);
  536. },
  537. (val) => {
  538. delete req.response[key];
  539. req.response[key] = val;
  540. }
  541. );
  542. }
  543. const resPromise = req.waitForResponseKeys().then(() => {
  544. for (const key of xhrResponses) {
  545. if (!(key in req.response)) continue;
  546. ah.proxyProps[key] = {
  547. get: () => {
  548. const val = req.response[key];
  549. xhr.dispatchEvent(
  550. new CustomEvent("ajaxHooker_readResponse", {
  551. detail: { key, val },
  552. })
  553. );
  554. return val;
  555. },
  556. };
  557. }
  558. });
  559. xhr.addEventListener("ajaxHooker_readResponse", (e) => {
  560. const descriptor = getDescriptor(req.response, e.detail.key);
  561. if (!descriptor || "get" in descriptor) {
  562. req.response[e.detail.key] = e.detail.val;
  563. }
  564. });
  565. const eventsClone = ah.proxyEvents.clone();
  566. ah.eventTrigger = (event) =>
  567. resPromise.then(() => eventsClone.trigger(event));
  568. ah.eventTrigger(e.detail);
  569. },
  570. { once: true }
  571. );
  572. realSend.call(xhr, data);
  573. });
  574. };
  575. },
  576. };
  577. function fakeXhr() {
  578. const xhr = new realXhr();
  579. let ah = xhr.__ajaxHooker;
  580. let xhrProxy = xhr;
  581. if (!ah) {
  582. const proxyEvents = new XhrEvents();
  583. ah = xhr.__ajaxHooker = {
  584. headers: {},
  585. originalXhr: xhr,
  586. proxyProps: {},
  587. proxyEvents: proxyEvents,
  588. eventTrigger: (e) => proxyEvents.trigger(e),
  589. toJSON: emptyFn, // Converting circular structure to JSON
  590. };
  591. xhrProxy = new Proxy(xhr, {
  592. get(target, prop) {
  593. try {
  594. if (target === xhr) {
  595. if (prop in ah.proxyProps) {
  596. const descriptor = ah.proxyProps[prop];
  597. return descriptor.get ? descriptor.get() : descriptor.value;
  598. }
  599. if (typeof xhr[prop] === "function") return xhr[prop].bind(xhr);
  600. }
  601. } catch (err) {
  602. console.error(err);
  603. }
  604. return target[prop];
  605. },
  606. set(target, prop, value) {
  607. try {
  608. if (target === xhr && prop in ah.proxyProps) {
  609. const descriptor = ah.proxyProps[prop];
  610. descriptor.set
  611. ? descriptor.set(value)
  612. : (descriptor.value = value);
  613. } else {
  614. target[prop] = value;
  615. }
  616. } catch (err) {
  617. console.error(err);
  618. }
  619. return true;
  620. },
  621. });
  622. xhr.addEventListener("readystatechange", xhrMethods.readyStateChange);
  623. xhr.addEventListener("load", xhrMethods.asyncListener);
  624. xhr.addEventListener("loadend", xhrMethods.asyncListener);
  625. for (const evt of xhrAsyncEvents) {
  626. const onEvt = "on" + evt;
  627. ah.proxyProps[onEvt] = {
  628. get: () => proxyEvents.events[onEvt] || null,
  629. set: (val) => proxyEvents.add(onEvt, val),
  630. };
  631. }
  632. for (const method of [
  633. "setRequestHeader",
  634. "addEventListener",
  635. "removeEventListener",
  636. "open",
  637. ]) {
  638. ah.proxyProps[method] = { value: xhrMethods[method] };
  639. }
  640. }
  641. ah.proxyProps.send = { value: xhrMethods.sendFactory(xhr.send) };
  642. return xhrProxy;
  643. }
  644. function hookFetchResponse(response, req) {
  645. for (const key of fetchResponses) {
  646. response[key] = () =>
  647. new Promise((resolve, reject) => {
  648. if (key in req.response) return resolve(req.response[key]);
  649. resProto[key].call(response).then((res) => {
  650. req.response[key] = res;
  651. req.waitForResponseKeys().then(() => {
  652. resolve(key in req.response ? req.response[key] : res);
  653. });
  654. }, reject);
  655. });
  656. }
  657. }
  658. function fakeFetch(url, options = {}) {
  659. if (!url) return realFetch.call(win, url, options);
  660. let init = { ...options };
  661. if (toString.call(url) === "[object Request]") {
  662. init = {};
  663. for (const prop of fetchInitProps) init[prop] = url[prop];
  664. Object.assign(init, options);
  665. url = url.url;
  666. }
  667. url = url.toString();
  668. init.method = init.method || "GET";
  669. init.headers = init.headers || {};
  670. if (shouldFilter("fetch", url, init.method, true))
  671. return realFetch.call(win, url, init);
  672. const request = {
  673. type: "fetch",
  674. url: url,
  675. method: init.method.toUpperCase(),
  676. abort: false,
  677. headers: parseHeaders(init.headers),
  678. data: init.body,
  679. response: null,
  680. async: true,
  681. };
  682. const req = new AHRequest(request);
  683. return new Promise((resolve, reject) => {
  684. req
  685. .waitForRequestKeys()
  686. .then(() => {
  687. if (request.abort)
  688. return reject(new DOMException("aborted", "AbortError"));
  689. init.method = request.method;
  690. init.headers = request.headers;
  691. init.body = request.data;
  692. realFetch.call(win, request.url, init).then((response) => {
  693. if (typeof request.response === "function") {
  694. req.response = {
  695. finalUrl: response.url,
  696. status: response.status,
  697. responseHeaders: parseHeaders(response.headers),
  698. };
  699. hookFetchResponse(response, req);
  700. response.clone = () => {
  701. const resClone = resProto.clone.call(response);
  702. hookFetchResponse(resClone, req);
  703. return resClone;
  704. };
  705. }
  706. resolve(response);
  707. }, reject);
  708. })
  709. .catch((err) => {
  710. console.error(err);
  711. resolve(realFetch.call(win, url, init));
  712. });
  713. });
  714. }
  715. win.XMLHttpRequest = fakeXhr;
  716. Object.keys(realXhr).forEach((key) => (fakeXhr[key] = realXhr[key]));
  717. fakeXhr.prototype = realXhr.prototype;
  718. win.fetch = fakeFetch;
  719. return {
  720. hook: (fn) => hookFns.push(fn),
  721. filter: (arr) => {
  722. filter = Array.isArray(arr) && arr;
  723. },
  724. protect: () => {
  725. readonly(win, "XMLHttpRequest", fakeXhr);
  726. readonly(win, "fetch", fakeFetch);
  727. },
  728. unhook: () => {
  729. writable(win, "XMLHttpRequest", realXhr);
  730. writable(win, "fetch", realFetch);
  731. },
  732. };
  733. };
  734.  
  735. /**
  736. * 根据坐标点击canvas元素的内部位置
  737. * @param {HTMLCanvasElement} canvasElement 画布元素
  738. * @param {number} [clientX=0] X坐标,默认值0
  739. * @param {number} [clientY=0] Y坐标,默认值0
  740. * @param {window} [view=globalThis] 触发的事件目标
  741. */
  742. Utils.canvasClickByPosition = function (
  743. canvasElement,
  744. clientX = 0,
  745. clientY = 0,
  746. view = globalThis
  747. ) {
  748. if (!canvasElement instanceof HTMLCanvasElement) {
  749. throw new Error(
  750. "Utils.canvasClickByPosition 参数canvasElement必须是canvas元素"
  751. );
  752. }
  753. clientX = parseInt(clientX);
  754. clientY = parseInt(clientY);
  755. const eventInit = {
  756. cancelBubble: true,
  757. cancelable: true,
  758. clientX: clientX,
  759. clientY: clientY,
  760. view: view,
  761. detail: 1,
  762. };
  763. canvas.dispatchEvent(new MouseEvent("mousedown", eventInit));
  764. canvas.dispatchEvent(new MouseEvent("mouseup", eventInit));
  765. };
  766.  
  767. /**
  768. * 【手机】检测点击的地方是否在该元素区域内
  769. * @param {Element|Node} element 需要检测的元素
  770. * @returns {boolean}
  771. * + true 点击在元素上
  772. * + false 未点击在元素上
  773. * @example
  774. * Utils.checkUserClickInNode(document.querySelector(".xxx"));
  775. * > false
  776. **/
  777. Utils.checkUserClickInNode = function (element) {
  778. if (!Utils.isDOM(element)) {
  779. throw new Error(
  780. "Utils.checkUserClickInNode 参数 targetNode 必须为 Element|Node 类型"
  781. );
  782. }
  783. let mouseClickPosX = Number(window.event.clientX); /* 鼠标相对屏幕横坐标 */
  784. let mouseClickPosY = Number(window.event.clientY); /* 鼠标相对屏幕纵坐标 */
  785. let elementPosXLeft = Number(
  786. element.getBoundingClientRect().left
  787. ); /* 要检测的元素的相对屏幕的横坐标最左边 */
  788. let elementPosXRight = Number(
  789. element.getBoundingClientRect().right
  790. ); /* 要检测的元素的相对屏幕的横坐标最右边 */
  791. let elementPosYTop = Number(
  792. element.getBoundingClientRect().top
  793. ); /* 要检测的元素的相对屏幕的纵坐标最上边 */
  794. let elementPosYBottom = Number(
  795. element.getBoundingClientRect().bottom
  796. ); /* 要检测的元素的相对屏幕的纵坐标最下边 */
  797. let clickNodeHTML = window.event?.target?.innerHTML;
  798. if (
  799. mouseClickPosX >= elementPosXLeft &&
  800. mouseClickPosX <= elementPosXRight &&
  801. mouseClickPosY >= elementPosYTop &&
  802. mouseClickPosY <= elementPosYBottom
  803. ) {
  804. return true;
  805. } else if (clickNodeHTML && element.innerHTML.includes(clickNodeHTML)) {
  806. /* 这种情况是应对在界面中隐藏的元素,getBoundingClientRect获取的都是0 */
  807. return true;
  808. } else {
  809. return false;
  810. }
  811. };
  812.  
  813. /**
  814. * 函数重载实现
  815. * @example
  816. * let getUsers = Utils.createOverload();
  817. * getUsers.addImpl("",()=>{
  818. * console.log("无参数");
  819. * });
  820. *
  821. * getUsers.addImpl("boolean",()=>{
  822. * console.log("boolean");
  823. * });
  824. *
  825. * getUsers.addImpl("string",()=>{
  826. * console.log("string");
  827. * });
  828. *
  829. * getUsers.addImpl("number","string",()=>{
  830. * console.log("number string");
  831. * });
  832. */
  833. Utils.createOverload = function () {
  834. let fnMap = new Map();
  835. function overload(...args) {
  836. let key = args.map((it) => typeof it).join(",");
  837. let fn = fnMap.get(key);
  838. if (!fn) {
  839. throw new TypeError("没有找到对应的实现");
  840. }
  841. return fn.apply(this, args);
  842. }
  843. overload.addImpl = function (...args) {
  844. let fn = args.pop();
  845. if (typeof fn !== "function") {
  846. throw new TypeError("最后一个参数必须是函数");
  847. }
  848. let key = args.join(",");
  849. fnMap.set(key, fn);
  850. };
  851. return overload;
  852. };
  853.  
  854. /**
  855. * 删除某个父元素,父元素可能在上层或上上层或上上上层...
  856. * @param {Element|Node} element 当前元素
  857. * @param {string} targetSelector 判断是否满足父元素,参数为当前处理的父元素,满足返回true,否则false
  858. * @returns {boolean}
  859. * + true 已删除
  860. * + false 未删除
  861. * @example
  862. * Utils.deleteParentNode(document.querySelector("a"),".xxx");
  863. * > true
  864. **/
  865. Utils.deleteParentNode = function (element, targetSelector) {
  866. if (element == null) {
  867. throw new Error("Utils.deleteParentNode 参数 target 不能为 null");
  868. }
  869. if (!Utils.isDOM(element)) {
  870. throw new Error(
  871. "Utils.deleteParentNode 参数 target 必须为 Node|HTMLElement 类型"
  872. );
  873. }
  874. if (typeof targetSelector !== "string") {
  875. throw new Error(
  876. "Utils.deleteParentNode 参数 targetSelector 必须为 string 类型"
  877. );
  878. }
  879. let result = false;
  880. let needRemoveDOM = element.closest(targetSelector);
  881. if (needRemoveDOM) {
  882. needRemoveDOM.remove();
  883. result = true;
  884. }
  885. return result;
  886. };
  887.  
  888. /**
  889. * 字典
  890. * @example
  891. * let dictionary = new Utils.Dictionary();
  892. * let dictionary2 = new Utils.Dictionary();
  893. * dictionary.set("test","111");
  894. * dictionary.get("test");
  895. * > '111'
  896. * dictionary.has("test");
  897. * > true
  898. * dictionary.concat(dictionary2);
  899. **/
  900. Utils.Dictionary = function () {
  901. this.items = {};
  902. /**
  903. * 检查是否有某一个键
  904. * @param {any} key 键
  905. * @returns {boolean}
  906. */
  907. this.has = function (key) {
  908. return this.items.hasOwnProperty(key);
  909. };
  910. /**
  911. * 为字典添加某一个值
  912. * @param {any} key 键
  913. * @param {any} val 值,默认为""
  914. */
  915. this.set = function (key, val = "") {
  916. if (key === void 0) {
  917. throw new Error("Utils.Dictionary().set 参数 key 不能为空");
  918. }
  919. this.items[key] = val;
  920. };
  921. /**
  922. * 删除某一个键
  923. * @param {any} key 键
  924. * @returns {boolean}
  925. */
  926. this.delete = function (key) {
  927. if (this.has(key)) {
  928. delete this.items[key];
  929. return true;
  930. }
  931. return false;
  932. };
  933. /**
  934. * 获取某个键的值
  935. * @param {any} key 键
  936. * @returns {any|undefined}
  937. */
  938. this.get = function (key) {
  939. return this.has(key) ? this.items[key] : void 0;
  940. };
  941. /**
  942. * 返回字典中的所有值
  943. * @returns {[...any]}
  944. */
  945. this.values = function () {
  946. let resultList = [];
  947. for (let prop in this.items) {
  948. if (this.has(prop)) {
  949. resultList.push(this.items[prop]);
  950. }
  951. }
  952. return resultList;
  953. };
  954. /**
  955. * 清空字典
  956. */
  957. this.clear = function () {
  958. this.items = {};
  959. };
  960. /**
  961. * 获取字典的长度
  962. * @returns {number}
  963. */
  964. this.size = function () {
  965. return Object.keys(this.items).length;
  966. };
  967. /**
  968. * 获取字典所有的键
  969. * @returns
  970. */
  971. this.keys = function () {
  972. return Object.keys(this.items);
  973. };
  974. /**
  975. * 返回字典本身
  976. * @returns
  977. */
  978. this.getItems = function () {
  979. return this.items;
  980. };
  981. /**
  982. * 合并另一个字典
  983. * @param {Dictionary} data 需要合并的字典
  984. */
  985. this.concat = function (data) {
  986. this.items = Utils.assign(this.items, data.getItems());
  987. };
  988. };
  989.  
  990. /**
  991. * 主动触发事件
  992. * @param {HTMLElement} element 元素
  993. * @param {string|[...string]} eventName 事件名称,可以是字符串,也可是字符串格式的列表
  994. * @param {object|undefined} details 赋予触发的Event的额外属性
  995. * + true 使用Proxy代理Event并设置获取isTrusted永远为True
  996. * + false (默认) 不对Event进行Proxy代理
  997. * @example
  998. * Utils.dispatchEvent(document.querySelector("input","input"))
  999. */
  1000. Utils.dispatchEvent = function (element, eventName, details) {
  1001. let eventNameList = [];
  1002. if (typeof eventName === "string") {
  1003. eventNameList = [eventName];
  1004. }
  1005. if (Array.isArray(eventName)) {
  1006. eventNameList = [...eventName];
  1007. }
  1008. eventNameList.forEach((_eventName_) => {
  1009. let event = new Event(_eventName_);
  1010. if (details) {
  1011. Object.assign(event, details);
  1012. }
  1013. element.dispatchEvent(event);
  1014. });
  1015. };
  1016.  
  1017. /**
  1018. * 下载base64格式的数据
  1019. * @param {string} base64Data 需要转换的base64数据
  1020. * @param {string} fileName 需要保存的文件名
  1021. * @example
  1022. * Utils.downloadBase64("data:image/jpeg:base64/,xxxxxx");
  1023. **/
  1024. Utils.downloadBase64 = function (base64Data, fileName) {
  1025. if (typeof base64Data !== "string") {
  1026. throw new Error(
  1027. "Utils.downloadBase64 参数 base64Data 必须为 string 类型"
  1028. );
  1029. }
  1030. if (typeof fileName !== "string") {
  1031. throw new Error("Utils.downloadBase64 参数 fileName 必须为 string 类型");
  1032. }
  1033. let aLink = document.createElement("a");
  1034. aLink.download = fileName;
  1035. aLink.href = base64Data;
  1036. aLink.click();
  1037. };
  1038.  
  1039. /**
  1040. * 选中页面中的文字,类似Ctrl+F的选中
  1041. * @param {string} [str=""] 需要寻找的字符串
  1042. * @param {boolean} [caseSensitive=false]
  1043. * + true 区分大小写
  1044. * + false (默认) 不区分大小写
  1045. * @returns {boolean|undefined}
  1046. * + true 找到
  1047. * + false 未找到
  1048. * + undefined 不可使用该Api
  1049. * @example
  1050. * Utils.findVisibleText("xxxxx");
  1051. * > true
  1052. **/
  1053. Utils.findWebPageVisibleText = function (str = "", caseSensitive = false) {
  1054. let TRange = null;
  1055. let strFound;
  1056. if (window.find) {
  1057. /* CODE FOR BROWSERS THAT SUPPORT window.find */
  1058. strFound = self.find(str, caseSensitive, true, true, false);
  1059. if (strFound && self.getSelection && !self.getSelection().anchorNode) {
  1060. strFound = self.find(str, caseSensitive, true, true, false);
  1061. }
  1062. if (!strFound) {
  1063. strFound = self.find(str, 0, 1);
  1064. while (self.find(str, 0, 1)) continue;
  1065. }
  1066. } else if (navigator.appName.indexOf("Microsoft") != -1) {
  1067. /* EXPLORER-SPECIFIC CODE */
  1068. if (TRange != null) {
  1069. TRange.collapse(false);
  1070. strFound = TRange.findText(str);
  1071. if (strFound) TRange.select();
  1072. }
  1073. if (TRange == null || strFound == 0) {
  1074. TRange = self.document.body.createTextRange();
  1075. strFound = TRange.findText(str);
  1076. if (strFound) TRange.select();
  1077. }
  1078. } else if (navigator.appName == "Opera") {
  1079. alert("Opera browsers not supported, sorry...");
  1080. return;
  1081. }
  1082. return strFound ? true : false;
  1083. };
  1084.  
  1085. /**
  1086. * 格式化byte为KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB
  1087. * @param {number} byteSize 字节
  1088. * @param {boolean} [addType=true]
  1089. * + true (默认) 添加单位
  1090. * + false 不添加单位
  1091. * @returns {string|number}
  1092. * + {string} 当addType为true时,且保留小数点末尾2位
  1093. * + {number} 当addType为false时,且保留小数点末尾2位
  1094. * @example
  1095. * Utils.formatByteToSize("812304");
  1096. * > '793.27KB'
  1097. * @example
  1098. * Utils.formatByteToSize("812304",false);
  1099. * > 793.27
  1100. **/
  1101. Utils.formatByteToSize = function (byteSize, addType = true) {
  1102. byteSize = parseInt(byteSize);
  1103. if (isNaN(byteSize)) {
  1104. throw new Error("Utils.formatByteToSize 参数 byteSize 格式不正确");
  1105. }
  1106. let result = 0;
  1107. let resultType = "KB";
  1108. let sizeData = {};
  1109. sizeData.B = 1;
  1110. sizeData.KB = 1024;
  1111. sizeData.MB = sizeData.KB * sizeData.KB;
  1112. sizeData.GB = sizeData.MB * sizeData.KB;
  1113. sizeData.TB = sizeData.GB * sizeData.KB;
  1114. sizeData.PB = sizeData.TB * sizeData.KB;
  1115. sizeData.EB = sizeData.PB * sizeData.KB;
  1116. sizeData.ZB = sizeData.EB * sizeData.KB;
  1117. sizeData.YB = sizeData.ZB * sizeData.KB;
  1118. sizeData.BB = sizeData.YB * sizeData.KB;
  1119. sizeData.NB = sizeData.BB * sizeData.KB;
  1120. sizeData.DB = sizeData.NB * sizeData.KB;
  1121. for (let key in sizeData) {
  1122. result = byteSize / sizeData[key];
  1123. resultType = key;
  1124. if (sizeData.KB >= result) {
  1125. break;
  1126. }
  1127. }
  1128. result = result.toFixed(2);
  1129. result = addType ? result + resultType.toString() : parseFloat(result);
  1130. return result;
  1131. };
  1132.  
  1133. /**
  1134. * 应用场景: 当你想要获取数组形式的元素时,它可能是其它的选择器,那么需要按照先后顺序填入参数
  1135. * 第一个是优先级最高的,依次下降,如果都没有,返回空列表
  1136. * 支持document.querySelectorAll、$("")、()=>{return document.querySelectorAll("")}
  1137. * @param {...NodeList|function} NodeList
  1138. * @returns {...Element}
  1139. * @example
  1140. * Utils.getNodeListValue(document.querySelectorAll("div.xxx"),document.querySelectorAll("a.xxx"));
  1141. * > [...div,div,div]
  1142. * @example
  1143. * Utils.getNodeListValue(divGetFunction,aGetFunction);
  1144. * > [...div,div,div]
  1145. */
  1146. Utils.getNodeListValue = function () {
  1147. let resultArray = [];
  1148. for (let arg of arguments) {
  1149. let value = arg;
  1150. if (typeof arg === "function") {
  1151. /* 方法 */
  1152. value = arg();
  1153. }
  1154. if (value.length !== 0) {
  1155. resultArray = [...value];
  1156. break;
  1157. }
  1158. }
  1159. return resultArray;
  1160. };
  1161.  
  1162. /**
  1163. * 获取格式化后的时间
  1164. * @param {string|number} [text= new Date()] 需要格式化的字符串或者时间戳
  1165. * @param {string} [formatType = "yyyy-MM-dd HH:mm:ss"] 格式化成的显示类型
  1166. * + yyyy 年
  1167. * + MM 月
  1168. * + dd 天
  1169. * + HH 时 (24小时制)
  1170. * + hh 时 (12小时制)
  1171. * + mm 分
  1172. * + ss 秒
  1173. * @returns {string} 返回格式化后的时间
  1174. * @example
  1175. * Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss");
  1176. * > '23:59:00'
  1177. * @example
  1178. * Utils.formatTime(1899187424988,"HH:mm:ss");
  1179. * > '15:10:13'
  1180. * @example
  1181. * Utils.formatTime()
  1182. * > '2023-1-1 00:00:00'
  1183. **/
  1184. Utils.formatTime = function (
  1185. text = new Date(),
  1186. formatType = "yyyy-MM-dd HH:mm:ss"
  1187. ) {
  1188. let time = text == null ? new Date() : new Date(text);
  1189. /**
  1190. * 校验时间补0
  1191. * @param {number} timeNum
  1192. * @returns
  1193. */
  1194. function checkTime(timeNum) {
  1195. if (timeNum < 10) return "0" + timeNum;
  1196. return timeNum;
  1197. }
  1198.  
  1199. /**
  1200. * 时间制修改 24小时制转12小时制
  1201. * @param {number} hourNum 小时
  1202. * @returns
  1203. */
  1204. function timeSystemChange(hourNum) {
  1205. return hourNum > 12 ? hourNum - 12 : hourNum;
  1206. }
  1207.  
  1208. let timeRegexp = {
  1209. yyyy: time.getFullYear(),
  1210. /* 年 */
  1211. MM: checkTime(time.getMonth() + 1),
  1212. /* 月 */
  1213. dd: checkTime(time.getDate()),
  1214. /* 日 */
  1215. HH: checkTime(time.getHours()),
  1216. /* 时 (24小时制) */
  1217. hh: checkTime(timeSystemChange(time.getHours())),
  1218. /* 时 (12小时制) */
  1219. mm: checkTime(time.getMinutes()),
  1220. /* 分 */
  1221. ss: checkTime(time.getSeconds()),
  1222. /* 秒 */
  1223. };
  1224. Object.keys(timeRegexp).forEach(function (key) {
  1225. let replaecRegexp = new RegExp(key, "g");
  1226. formatType = formatType.replace(replaecRegexp, timeRegexp[key]);
  1227. });
  1228. return formatType;
  1229. };
  1230.  
  1231. /**
  1232. * 字符串格式的时间转时间戳
  1233. * @param {string} text 字符串格式的时间,例如:
  1234. * + 2022-11-21 00:00:00
  1235. * + 00:00:00
  1236. * @returns {number} 返回时间戳
  1237. * @example
  1238. * Utils.formatToTimeStamp("2022-11-21 00:00:00");
  1239. * > 1668960000000
  1240. **/
  1241. Utils.formatToTimeStamp = function (text) {
  1242. /* 把字符串格式的时间(完整,包括日期和时间)格式化成时间 */
  1243. if (typeof text !== "string") {
  1244. throw new Error("Utils.formatToTimeStamp 参数 text 必须为 string 类型");
  1245. }
  1246. if (text.length === 8) {
  1247. /* 该字符串只有时分秒 */
  1248. let today = new Date();
  1249. text =
  1250. today.getFullYear() +
  1251. "-" +
  1252. (today.getMonth() + 1) +
  1253. "-" +
  1254. today.getDate() +
  1255. " " +
  1256. text;
  1257. }
  1258. text = text.substring(0, 19);
  1259. text = text.replace(/-/g, "/");
  1260. let timestamp = new Date(text).getTime();
  1261. return timestamp;
  1262. };
  1263.  
  1264. /**
  1265. * gbk格式的url编码,来自https://greasyfork.org/zh-CN/scripts/427726-gbk-url-js
  1266. * @example
  1267. * let gbkEncoder = new Utils.GBKEncoder();
  1268. * gbkEncoder.encode("测试");
  1269. * > '%B2%E2%CA%D4'
  1270. * gbkEncoder.decode("%B2%E2%CA%D4");
  1271. * > 测试
  1272. */
  1273. Utils.GBKEncoder = function () {
  1274. function handleText(text) {
  1275. text = text
  1276. .replace(/#(\d+)\$/g, function (a, b) {
  1277. return Array(+b + 3).join("#");
  1278. })
  1279. .replace(/#/g, "####")
  1280. .replace(/(\w\w):([\w#]+)(?:,|$)/g, function (a, hd, dt) {
  1281. return dt.replace(/../g, function (a) {
  1282. if (a != "##") {
  1283. return hd + a;
  1284. } else {
  1285. return a;
  1286. }
  1287. });
  1288. });
  1289. return text;
  1290. }
  1291. function handleHash() {
  1292. let index = 0;
  1293. data = data.match(/..../g);
  1294. for (let i = 0x81; i <= 0xfe; i++) {
  1295. for (let j = 0x40; j <= 0xfe; j++) {
  1296. U2Ghash[data[index++]] = (
  1297. "%" +
  1298. i.toString(16) +
  1299. "%" +
  1300. j.toString(16)
  1301. ).toUpperCase();
  1302. }
  1303. }
  1304. for (let key in U2Ghash) {
  1305. G2Uhash[U2Ghash[key]] = key;
  1306. }
  1307. }
  1308. function isAscii(unicode) {
  1309. return unicode <= 0x007f && unicode >= 0x0000;
  1310. }
  1311. let data = handleText(
  1312. "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"
  1313. );
  1314. let U2Ghash = {};
  1315. let G2Uhash = {};
  1316. handleHash();
  1317. /**
  1318. * 编码
  1319. * @param {string} str
  1320. * @returns {string}
  1321. */
  1322. this.encode = function (str) {
  1323. return [...str].reduce((result, val, i) => {
  1324. return result + toGBK(val);
  1325. }, "");
  1326. function toGBK(val) {
  1327. let result = "";
  1328. for (let i = 0; i < val.length; i++) {
  1329. const codePoint = val.codePointAt(i);
  1330. const code = String.fromCodePoint(codePoint);
  1331. let key = codePoint.toString(16);
  1332. key.length != 4 && (key = ("000" + key).match(/....$/)[0]);
  1333.  
  1334. /* Add up i by code.length */
  1335. i += code.length - 1;
  1336.  
  1337. /* If code is in ascii range */
  1338. if (isAscii(codePoint)) {
  1339. result += encodeURIComponent(code);
  1340. continue;
  1341. }
  1342.  
  1343. /* If Got encoded string from U2Ghash */
  1344. if (U2Ghash[key]) {
  1345. result += U2Ghash[key];
  1346. continue;
  1347. }
  1348.  
  1349. /*
  1350. If 2 or more char combines to one visible code,
  1351. or just this code is not in GBK
  1352. */
  1353. result += toGBK(`&#${codePoint};`);
  1354. }
  1355. return result;
  1356. }
  1357. };
  1358.  
  1359. /**
  1360. * 解码
  1361. * @param {string} str
  1362. * @returns {string}
  1363. */
  1364. this.decode = function (str) {
  1365. var GBKMatcher = /%[0-9A-F]{2}%[0-9A-F]{2}/;
  1366. var UTFMatcher = /%[0-9A-F]{2}/;
  1367. var gbk = true,
  1368. utf = true;
  1369. while (utf) {
  1370. gbk = str.match(GBKMatcher);
  1371. utf = str.match(UTFMatcher);
  1372. if (gbk && gbk in G2Uhash) {
  1373. str = str.replace(gbk, String.fromCharCode("0x" + G2Uhash[gbk]));
  1374. } else {
  1375. str = str.replace(utf, decodeURIComponent(utf));
  1376. }
  1377. }
  1378. return str;
  1379. };
  1380. return this;
  1381. };
  1382.  
  1383. /**
  1384. * 获取NodeList或Array对象中的最后一个的值
  1385. * @param {NodeList|Array} targetObj
  1386. * @returns {any}
  1387. * @example
  1388. * Utils.getArrayLastValue(document.querySelectorAll("div"));
  1389. * > div
  1390. * @example
  1391. * Utils.getArrayLastValue([1,2,3,4,5]);
  1392. * > 5
  1393. */
  1394. Utils.getArrayLastValue = function (targetObj) {
  1395. return targetObj[targetObj.length - 1];
  1396. };
  1397.  
  1398. /**
  1399. * 使用场景:当想获取的元素可能是不同的选择器的时候,按顺序优先级获取
  1400. * 参数类型可以是Element或者是Function
  1401. * @returns {any} 如果都没有的话,返回null
  1402. * @example
  1403. * // 如果a.aaa不存在的话,取a.bbb,这里假设a.aaa不存在
  1404. * Utils.getArrayRealValue(document.querySelector("a.aaa"),document.querySelector("a.bbb"));
  1405. * > a.bbb
  1406. * @example
  1407. * Utils.getArrayRealValue(()=>{return document.querySelector("a.aaa").href},()=>{document.querySelector("a.bbb").getAttribute("data-href")});
  1408. * > javascript:;
  1409. */
  1410. Utils.getArrayRealValue = function () {
  1411. let result = null;
  1412. for (let arg of arguments) {
  1413. if (typeof arg === "function") {
  1414. /* 方法 */
  1415. arg = arg();
  1416. }
  1417. if (arg != null) {
  1418. result = arg;
  1419. break;
  1420. }
  1421. }
  1422. return result;
  1423. };
  1424.  
  1425. /**
  1426. * 获取天数差异,如何获取某个时间与另一个时间相差的天数
  1427. * @param {number} [timestamp1= new Date().getTime()] 时间戳(毫秒|秒),不区分哪个更大
  1428. * @param {number} [timestamp2= new Date().getTime()] 时间戳(毫秒|秒),不区分哪个更大
  1429. * @param {"年"|"月"|"天"|"时"|"分"|"秒"|"auto"} [type= "天"] 返回的数字的表达的类型,比如:年、月、天、时、分、秒、auto,默认天
  1430. * @returns {number}
  1431. * @example
  1432. * Utils.getDaysDifference(new Date().getTime());
  1433. * > 0
  1434. * @example
  1435. * Utils.getDaysDifference(new Date().getTime(),undefined,"秒");
  1436. * > 0
  1437. */
  1438. Utils.getDaysDifference = function (
  1439. timestamp1 = new Date().getTime(),
  1440. timestamp2 = new Date().getTime(),
  1441. type = "天"
  1442. ) {
  1443. type = type.trim();
  1444. if (timestamp1.toString().length === 10) {
  1445. timestamp1 = timestamp1 * 1000;
  1446. }
  1447. if (timestamp2.toString().length === 10) {
  1448. timestamp2 = timestamp2 * 1000;
  1449. }
  1450. let smallTimeStamp = timestamp1 > timestamp2 ? timestamp2 : timestamp1;
  1451. let bigTimeStamp = timestamp1 > timestamp2 ? timestamp1 : timestamp2;
  1452. let oneSecond = 1000; /* 一秒的毫秒数 */
  1453. let oneMinute = 60 * oneSecond; /* 一分钟的毫秒数 */
  1454. let oneHour = 60 * oneMinute; /* 一小时的毫秒数 */
  1455. let oneDay = 24 * oneHour; /* 一天的毫秒数 */
  1456. let oneMonth = 30 * oneDay; /* 一个月的毫秒数(30天) */
  1457. let oneYear = 12 * oneMonth; /* 一年的毫秒数 */
  1458. let bigDate = new Date(bigTimeStamp);
  1459. let smallDate = new Date(smallTimeStamp);
  1460. let remainderValue = 1;
  1461. if (type === "年") {
  1462. remainderValue = oneYear;
  1463. } else if (type === "月") {
  1464. remainderValue = oneMonth;
  1465. } else if (type === "天") {
  1466. remainderValue = oneDay;
  1467. } else if (type === "时") {
  1468. remainderValue = oneHour;
  1469. } else if (type === "分") {
  1470. remainderValue = oneMinute;
  1471. } else if (type === "秒") {
  1472. remainderValue = oneSecond;
  1473. }
  1474. let diffValue = Math.round(
  1475. Math.abs((bigDate - smallDate) / remainderValue)
  1476. );
  1477. if (type === "auto") {
  1478. let timeDifference = bigTimeStamp - smallTimeStamp;
  1479. diffValue = Math.floor(timeDifference / (24 * 3600 * 1000));
  1480. if (diffValue > 0) {
  1481. diffValue = diffValue + "天";
  1482. } else {
  1483. /* 计算出小时数 */
  1484. let leave1 =
  1485. timeDifference % (24 * 3600 * 1000); /* 计算天数后剩余的毫秒数 */
  1486. let hours = Math.floor(leave1 / (3600 * 1000));
  1487. if (hours > 0) {
  1488. diffValue = hours + "小时";
  1489. } else {
  1490. /* 计算相差分钟数 */
  1491. let leave2 = leave1 % (3600 * 1000); /* 计算小时数后剩余的毫秒数 */
  1492. let minutes = Math.floor(leave2 / (60 * 1000));
  1493. if (minutes > 0) {
  1494. diffValue = minutes + "分钟";
  1495. } else {
  1496. /* 计算相差秒数 */
  1497. let leave3 = leave2 % (60 * 1000); /* 计算分钟数后剩余的毫秒数 */
  1498. let seconds = Math.round(leave3 / 1000);
  1499. diffValue = seconds + "秒";
  1500. }
  1501. }
  1502. }
  1503. }
  1504. return diffValue;
  1505. };
  1506.  
  1507. /**
  1508. * 获取元素的选择器字符串
  1509. * @param {HTMLElement} element
  1510. * @returns {string|undefined}
  1511. * @example
  1512. * Utils.getElementSelector(document.querySelector("a"))
  1513. * > '.....'
  1514. */
  1515. Utils.getElementSelector = function (element) {
  1516. if (!element) return;
  1517. if (!element.parentElement) return;
  1518. /* 如果元素有id属性,则直接返回id选择器 */
  1519. if (element.id) return "#" + element.id;
  1520.  
  1521. /* 递归地获取父元素的选择器 */
  1522. let selector = Utils.getElementSelector(element.parentElement);
  1523. if (!selector) {
  1524. return element.tagName;
  1525. }
  1526. /* 如果有多个相同类型的兄弟元素,则需要添加索引 */
  1527. if (element.parentElement.querySelectorAll(element.tagName).length > 1) {
  1528. let index =
  1529. Array.prototype.indexOf.call(element.parentElement.children, element) +
  1530. 1;
  1531. selector +=
  1532. " > " + element.tagName.toLowerCase() + ":nth-child(" + index + ")";
  1533. } else {
  1534. selector += " > " + element.tagName.toLowerCase();
  1535. }
  1536. return selector;
  1537. };
  1538.  
  1539. /**
  1540. * 获取最大值
  1541. * @returns {any}
  1542. * @example
  1543. * Utils.getMaxValue(1,3,5,7,9)
  1544. * > 9
  1545. * @example
  1546. * Utils.getMaxValue([1,3,5])
  1547. * > 5
  1548. * @example
  1549. * Utils.getMaxValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)})
  1550. * > 456
  1551. * @example
  1552. * Utils.getMaxValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)})
  1553. * > 2
  1554. */
  1555. Utils.getMaxValue = function () {
  1556. let result = [...arguments];
  1557. let newResult = [];
  1558. if (result.length > 1) {
  1559. if (
  1560. result.length === 2 &&
  1561. typeof result[0] === "object" &&
  1562. typeof result[1] === "function"
  1563. ) {
  1564. let data = result[0];
  1565. let handleDataFunc = result[1];
  1566. Object.keys(data).forEach((keyName) => {
  1567. newResult = [...newResult, handleDataFunc(keyName, data[keyName])];
  1568. });
  1569. } else {
  1570. result.forEach((item) => {
  1571. if (!isNaN(parseFloat(item))) {
  1572. newResult = [...newResult, parseFloat(item)];
  1573. }
  1574. });
  1575. }
  1576. return Math.max(...newResult);
  1577. } else if (result.length === 1) {
  1578. result[0].forEach((item) => {
  1579. if (!isNaN(parseFloat(item))) {
  1580. newResult = [...newResult, parseFloat(item)];
  1581. }
  1582. });
  1583. return Math.max(...newResult);
  1584. }
  1585. };
  1586.  
  1587. /**
  1588. * 获取页面中最大的z-index再+1
  1589. * @returns {number}
  1590. * @example
  1591. * Utils.getMaxZIndex();
  1592. * > 1001
  1593. **/
  1594. Utils.getMaxZIndex = function () {
  1595. let nodeIndexList = [];
  1596. document.querySelectorAll("*").forEach((element) => {
  1597. let nodeStyle = window.getComputedStyle(element);
  1598. /* 不对position为static和display为none的元素进行获取它们的z-index */
  1599. if (nodeStyle.position !== "static" && nodeStyle.display !== "none") {
  1600. nodeIndexList = nodeIndexList.concat(parseInt(nodeStyle.zIndex));
  1601. }
  1602. });
  1603. nodeIndexList = nodeIndexList.filter(Boolean); /* 过滤非Boolean类型 */
  1604. return nodeIndexList.length ? Math.max(...nodeIndexList) + 1 : 0;
  1605. };
  1606.  
  1607. /**
  1608. * 获取最小值
  1609. * @returns {any}
  1610. * @example
  1611. * Utils.getMinValue(1,3,5,7,9)
  1612. * > 1
  1613. * @example
  1614. * Utils.getMinValue([1,3,5])
  1615. * > 1
  1616. * @example
  1617. * Utils.getMinValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)})
  1618. * > 123
  1619. * @example
  1620. * Utils.getMinValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)})
  1621. * > 0
  1622. */
  1623. Utils.getMinValue = function () {
  1624. let result = [...arguments];
  1625. let newResult = [];
  1626. if (result.length > 1) {
  1627. if (
  1628. result.length === 2 &&
  1629. typeof result[0] === "object" &&
  1630. typeof result[1] === "function"
  1631. ) {
  1632. let data = result[0];
  1633. let handleDataFunc = result[1];
  1634. Object.keys(data).forEach((keyName) => {
  1635. newResult = [...newResult, handleDataFunc(keyName, data[keyName])];
  1636. });
  1637. } else {
  1638. result.forEach((item) => {
  1639. if (!isNaN(parseFloat(item))) {
  1640. newResult = [...newResult, parseFloat(item)];
  1641. }
  1642. });
  1643. }
  1644. return Math.min(...newResult);
  1645. } else if (result.length === 1) {
  1646. result[0].forEach((item) => {
  1647. if (!isNaN(parseFloat(item))) {
  1648. newResult = [...newResult, parseFloat(item)];
  1649. }
  1650. });
  1651. return Math.min(...newResult);
  1652. }
  1653. };
  1654.  
  1655. /**
  1656. * 获取随机的安卓手机User-Agent
  1657. * @returns {string} 返回随机字符串
  1658. * @example
  1659. * Utils.getRandomAndroidUA();
  1660. * > 'Mozilla/5.0 (Linux; Android 10; MI 13 Build/OPR1.170623.027; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.3490.40 Mobile Safari/537.36'
  1661. **/
  1662. Utils.getRandomAndroidUA = function () {
  1663. let androidVersion = Utils.getRandomValue(10, 14);
  1664. let mobileNameList = [
  1665. "LDN-LX3",
  1666. "RNE-L03",
  1667. "ASUS_X00ID Build/NMF26F",
  1668. "WAS-LX3",
  1669. "PRA-LX3",
  1670. "MYA-L03",
  1671. "Moto G Play",
  1672. "Moto C Build/NRD90M.063",
  1673. "Redmi Note 4 Build/NRD90M",
  1674. "HUAWEI VNS-L21 Build/HUAWEIVNS-L21",
  1675. "VTR-L09",
  1676. "TRT-LX3",
  1677. "M2003J15SC Build/RP1A.200720.011; wv",
  1678. "MI 13 Build/OPR1.170623.027; wv",
  1679. ];
  1680. let randomMobile = Utils.getRandomValue(mobileNameList);
  1681. let chromeVersion1 = Utils.getRandomValue(110, 120);
  1682. let chromeVersion2 = Utils.getRandomValue(0, 0);
  1683. let chromeVersion3 = Utils.getRandomValue(2272, 6099);
  1684. let chromeVersion4 = Utils.getRandomValue(1, 218);
  1685. return `Mozilla/5.0 (Linux; Android ${androidVersion}; ${randomMobile}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.${chromeVersion2}.${chromeVersion3}.${chromeVersion4} Mobile Safari/537.36`;
  1686. };
  1687.  
  1688. /**
  1689. * 获取随机值
  1690. * @returns {any}
  1691. * @example
  1692. * Utils.getRandomValue([1,2,3])
  1693. * > 3
  1694. * @example
  1695. * Utils.getRandomValue({1:"结果1",2:"结果2",3:"结果3"}})
  1696. * > 结果2
  1697. * @example
  1698. * Utils.getRandomValue(1,9)
  1699. * > 9
  1700. * @example
  1701. * Utils.getRandomValue(1,9,6,99)
  1702. * > 6
  1703. * @example
  1704. * Utils.getRandomValue({1:1},{2:2})
  1705. * > {1: 1}
  1706. * @example
  1707. * Utils.getRandomValue()
  1708. * > undefined
  1709. */
  1710. Utils.getRandomValue = function () {
  1711. let result = [...arguments];
  1712. if (result.length > 1) {
  1713. if (
  1714. result.length === 2 &&
  1715. typeof result[0] === "number" &&
  1716. typeof result[1] === "number"
  1717. ) {
  1718. let leftNumber = result[0] > result[1] ? result[1] : result[0];
  1719. let rightNumber = result[0] > result[1] ? result[0] : result[1];
  1720. return (
  1721. Math.round(Math.random() * (rightNumber - leftNumber)) + leftNumber
  1722. );
  1723. } else {
  1724. return result[Math.floor(Math.random() * result.length)];
  1725. }
  1726. } else if (result.length === 1) {
  1727. let paramData = result[0];
  1728. if (Array.isArray(paramData)) {
  1729. return paramData[Math.floor(Math.random() * paramData.length)];
  1730. } else if (
  1731. typeof paramData === "object" &&
  1732. Object.keys(paramData).length > 0
  1733. ) {
  1734. let paramObjDataKey =
  1735. Object.keys(paramData)[
  1736. Math.floor(Math.random() * Object.keys(paramData).length)
  1737. ];
  1738. return paramData[paramObjDataKey];
  1739. } else {
  1740. return paramData;
  1741. }
  1742. }
  1743. };
  1744. /**
  1745. * 获取随机的电脑端User-Agent
  1746. * + Mozilla/5.0:以前用于Netscape浏览器,目前大多数浏览器UA都会带有
  1747. * + Windows NT 13:代表Window11系统
  1748. * + Windows NT 10.0:代表Window10系统
  1749. * + Windows NT 6.1:代表windows7系统
  1750. * + WOW64:Windows-on-Windows 64-bit,32位的应用程序运行于此64位处理器上
  1751. * + Win64:64位
  1752. * + AppleWebKit/537.36:浏览器内核
  1753. * + KHTML:HTML排版引擎
  1754. * + like Gecko:这不是Geckeo 浏览器,但是运行起来像Geckeo浏览器
  1755. * + Chrome/106.0.5068.19:Chrome版本号
  1756. * + Safari/537.36:宣称自己是Safari?
  1757. * @returns {string} 返回随机字符串
  1758. * @example
  1759. * Utils.getRandomPCUA();
  1760. * > 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5068.19 Safari/537.36'
  1761. **/
  1762. Utils.getRandomPCUA = function () {
  1763. let chromeVersion1 = Utils.getRandomValue(110, 120);
  1764. let chromeVersion2 = Utils.getRandomValue(0, 0);
  1765. let chromeVersion3 = Utils.getRandomValue(2272, 6099);
  1766. let chromeVersion4 = Utils.getRandomValue(1, 218);
  1767. return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.${chromeVersion2}.${chromeVersion3}.${chromeVersion4} Safari/537.36`;
  1768. };
  1769.  
  1770. /**
  1771. * 获取元素上的使用React框架的实例属性,目前包括reactFiber、reactProps、reactEvents、reactEventHandlers、reactInternalInstance
  1772. * @param {HTMLElement} element 需要获取的目标元素
  1773. * @returns {object}
  1774. * @example
  1775. * Utils.getReactObj(document.querySelector("input"))?.reactProps?.onChange({target:{value:"123"}});
  1776. */
  1777. Utils.getReactObj = function (element) {
  1778. let result = {};
  1779. Object.keys(element).forEach((domPropsName) => {
  1780. if (domPropsName.startsWith("__react")) {
  1781. let propsName = domPropsName.replace(/__(.+)\$.+/i, "$1");
  1782. if (propsName in result) {
  1783. new Error("重复属性 " + domPropsName);
  1784. } else {
  1785. result[propsName] = element[domPropsName];
  1786. }
  1787. }
  1788. });
  1789. return result;
  1790. };
  1791.  
  1792. /**
  1793. * 获取文本的字符长度
  1794. * @param {string} text
  1795. * @returns {number}
  1796. * @example
  1797. * Utils.getTextLength("测试文本")
  1798. * > 12
  1799. */
  1800. Utils.getTextLength = function (text) {
  1801. let encoder = new TextEncoder();
  1802. let bytes = encoder.encode(text);
  1803. return bytes.length;
  1804. };
  1805.  
  1806. /**
  1807. * 获取文本占据的空间大小,返回自动的单位,如12 Kb,14 K,20 MB,1 GB
  1808. * @param {string} text 目标字符串
  1809. * @param {boolean} [addType=true]
  1810. * + true (默认) 自动添加单位
  1811. * + false 不添加单位
  1812. * @returns {string}
  1813. * @example
  1814. * Utils.getTextStorageSize("测试文本");
  1815. * > '12.00B'
  1816. */
  1817. Utils.getTextStorageSize = function (text, addType = true) {
  1818. return Utils.formatByteToSize(Utils.getTextLength(text), addType);
  1819. };
  1820.  
  1821. /**
  1822. * 在页面中增加style元素,如果html节点存在子节点,添加子节点第一个,反之,添加到html节点的子节点最后一个
  1823. * @param {string} cssText css字符串
  1824. * @returns {HTMLElement} 返回添加的CSS标签
  1825. * @example
  1826. * Utils.GM_addStyle("html{}");
  1827. * > <style type="text/css">html{}</style>
  1828. */
  1829. Utils.GM_addStyle = function (cssText) {
  1830. if (typeof cssText !== "string") {
  1831. throw new Error("Utils.GM_addStyle 参数cssText 必须为String类型");
  1832. }
  1833. let cssNode = document.createElement("style");
  1834. cssNode.setAttribute("type", "text/css");
  1835. cssNode.innerHTML = cssText;
  1836. if (document.documentElement.childNodes.length === 0) {
  1837. /* 插入body后 */
  1838. document.documentElement.appendChild(cssNode);
  1839. } else {
  1840. /* 插入head前面 */
  1841. document.documentElement.insertBefore(
  1842. cssNode,
  1843. document.documentElement.childNodes[0]
  1844. );
  1845. }
  1846. return cssNode;
  1847. };
  1848.  
  1849. /**
  1850. * @typedef {object} GM_Cookie_ListDetails
  1851. * @property {string} url 默认为当前的url
  1852. * @property {string} domain 默认为当前的域名(window.location.hostname)
  1853. * @property {string} name 需要检索的Cookie的名字
  1854. * @property {string} path 需要检索的Cookie的路径,默认为"/"
  1855. */
  1856.  
  1857. /**
  1858. * @typedef {object} GM_Cookie_SetDetails
  1859. * @property {string} url 默认为当前的url
  1860. * @property {string} domain 默认为当前的域名(window.location.hostname)
  1861. * @property {string} name 需要检索的Cookie的名字
  1862. * @property {string} path 需要检索的Cookie的路径,默认为"/"
  1863. * @property {string} value 值
  1864. * @property {boolean} secure
  1865. * @property {boolean} httpOnly
  1866. * @property {number} expirationDate Cookie过期时间,默认为30天
  1867. */
  1868.  
  1869. /**
  1870. * @typedef {object} GM_Cookie_DeleteDetails
  1871. * @property {string} url 默认为当前的url
  1872. * @property {string} name 需要检索的Cookie的名字
  1873. */
  1874.  
  1875. /**
  1876. * 对于GM_cookie的兼容写法,当无法使用GM_cookie时可以使用这个,但是并不完全兼容,有些写不出来且限制了httponly是无法访问的
  1877. * @example
  1878. let GM_cookie = new Utils.GM_Cookie();
  1879. GM_cookie.list({name:"xxx_cookie_xxx"},function(cookies,error){
  1880. if (!error) {
  1881. console.log(cookies);
  1882. console.log(cookies.value);
  1883. } else {
  1884. console.error(error);
  1885. }
  1886. });
  1887. GM_cookie.set({name:"xxx_cookie_test_xxx",value:"这是Cookie测试值"},function(error){
  1888. if (error) {
  1889. console.error(error);
  1890. } else {
  1891. console.log('Cookie set successfully.');
  1892. }
  1893. })
  1894. GM_cookie.delete({name:"xxx_cookie_test_xxx"},function(error){
  1895. if (error) {
  1896. console.error(error);
  1897. } else {
  1898. console.log('Cookie set successfully.');
  1899. }
  1900. })
  1901. **/
  1902. Utils.GM_Cookie = function () {
  1903. /**
  1904. * 获取Cookie
  1905. * @param {GM_Cookie_ListDetails} [paramDetails={}]
  1906. * @param {function|undefined} callback
  1907. * + cookies object[]
  1908. * + error string|undefined
  1909. */
  1910. this.list = function (paramDetails = {}, callback = () => {}) {
  1911. let resultData = [];
  1912. try {
  1913. let details = {
  1914. url: window.location.href,
  1915. domain: window.location.hostname,
  1916. name: "",
  1917. path: "/",
  1918. };
  1919. details = Utils.assign(details, paramDetails);
  1920. let cookies = document.cookie.split(";");
  1921. cookies.forEach((item) => {
  1922. item = item.trimStart();
  1923. let itemName = item.split("=")[0];
  1924. let itemValue = item.replace(new RegExp("^" + itemName + "="), "");
  1925. let nameRegexp =
  1926. details.name instanceof RegExp
  1927. ? details.name
  1928. : new RegExp("^" + details.name, "g");
  1929. if (itemName.match(nameRegexp)) {
  1930. resultData = [
  1931. ...resultData,
  1932. {
  1933. domain: window.location.hostname,
  1934. expirationDate: void 0,
  1935. hostOnly: true,
  1936. httpOnly: false,
  1937. name: itemName,
  1938. path: "/",
  1939. sameSite: "unspecified",
  1940. secure: true,
  1941. session: false,
  1942. value: itemValue,
  1943. },
  1944. ];
  1945. return;
  1946. }
  1947. });
  1948. callback(resultData);
  1949. } catch (error) {
  1950. callback(resultData, error);
  1951. }
  1952. };
  1953.  
  1954. /**
  1955. * 设置Cookie
  1956. * @param {GM_Cookie_SetDetails} [paramDetails={}]
  1957. * @param {function|undefined} callback
  1958. */
  1959. this.set = function (paramDetails = {}, callback = () => {}) {
  1960. try {
  1961. let details = {
  1962. url: window.location.href,
  1963. name: "",
  1964. value: "",
  1965. domain: window.location.hostname,
  1966. path: "/",
  1967. secure: true,
  1968. httpOnly: false,
  1969. /**
  1970. * Expires in 30 days
  1971. */
  1972. expirationDate: Math.floor(Date.now()) + 60 * 60 * 24 * 30,
  1973. };
  1974. details = Utils.assign(details, paramDetails);
  1975. let life = details.expirationDate
  1976. ? details.expirationDate
  1977. : Math.floor(Date.now()) + 60 * 60 * 24 * 30;
  1978. let cookieStr =
  1979. details.name +
  1980. "=" +
  1981. decodeURIComponent(details.value) +
  1982. ";expires=" +
  1983. new Date(life).toGMTString() +
  1984. "; path=/";
  1985. document.cookie = cookieStr;
  1986. callback();
  1987. } catch (error) {
  1988. callback(error);
  1989. }
  1990. };
  1991.  
  1992. /**
  1993. * 删除Cookie
  1994. * @param {GM_Cookie_DeleteDetails} [paramDetails={}]
  1995. * @param {function|undefined} callback
  1996. */
  1997. this.delete = (paramDetails = {}, callback = () => {}) => {
  1998. try {
  1999. let details = {
  2000. url: window.location.href,
  2001. name: "",
  2002. firstPartyDomain: "",
  2003. };
  2004. details = Utils.assign(details, paramDetails);
  2005. let cookieStr =
  2006. details.name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
  2007. document.cookie = cookieStr;
  2008. callback();
  2009. } catch (error) {
  2010. callback(error);
  2011. }
  2012. };
  2013. };
  2014.  
  2015. /**
  2016. * @typedef {object} GM_Menu_CallBack
  2017. * @property {string} key 当前菜单键名
  2018. * @property {boolean} enable 当前菜单enable值
  2019. * @property {boolean} oldEnable 点击之前enable值
  2020. * @property {MouseEvent|KeyboardEvent} event 触发事件
  2021. * @property { (enable: boolean)=> {} } storeValue 将enable值写入本地
  2022. */
  2023.  
  2024. /**
  2025. * @typedef {object} GM_Menu_Details
  2026. * @property {string} key (必须)菜单的本地键key,不可重复,会覆盖
  2027. * @property {string} text (必须)菜单的文本
  2028. * @property {boolean|undefined} enable 菜单的开启状态,默认为false
  2029. * @property {number|undefined} id 使用条件:TamperMonkey版本>5.0,如果id和已注册的菜单id相同,可修改当前已注册菜单的options
  2030. * @property {string|undefined} accessKey
  2031. * @property {boolean|undefined} autoClose 自动关闭菜单,可不设置
  2032. * @property {string|undefined} title 使用条件:TamperMonkey版本>5.0,使用菜单项的鼠标悬浮上的工具提示,可为空
  2033. * @property {boolean|undefined} autoReload 点击菜单后自动刷新网页,默认为true
  2034. * @property { (text:string,enable:boolean)=>{} } showText 菜单的显示文本,未设置的话则自动根据enable在前面加上图标
  2035. * @property {(data: GM_Menu_CallBack)=>{}} callback 点击菜单的回调
  2036. * @property { boolean } [isStoreValue=true] 是否允许菜单进行存储值,默认true允许
  2037. */
  2038.  
  2039. /**
  2040. * @typedef {object} GM_Menu_Config
  2041. * @property {GM_Menu_Details[]} data 配置,可为空
  2042. * @property {boolean|undefined} autoReload 全局菜单点击菜单后自动刷新网页,默认为true
  2043. * @property {function} GM_getValue (必须)油猴函数@grant GM_getValue
  2044. * @property {function} GM_setValue (必须)油猴函数@grant GM_setValue
  2045. * @property {function} GM_registerMenuCommand (必须)油猴函数@grant GM_registerMenuCommand
  2046. * @property {function} GM_unregisterMenuCommand (必须)油猴函数@grant GM_unregisterMenuCommand
  2047. */
  2048. /**
  2049. * 注册油猴菜单,要求本地存储的键名不能存在其它键名`GM_Menu_Local_Map`会冲突/覆盖
  2050. * @param { GM_Menu_Config } details 传递的菜单配置
  2051. * @example
  2052. let GM_Menu = new Utils.GM_Menu({
  2053. data: [
  2054. {
  2055. menu_key: "menu_key",
  2056. text: "测试按钮",
  2057. enable: true,
  2058. accessKey: "a",
  2059. autoClose: false,
  2060. showText(text, enable) {
  2061. return "[" + (enable ? "√" : "×") + "]" + text;
  2062. },
  2063. callback(data) {
  2064. console.log("点击菜单,值修改为", data.enable);
  2065. },
  2066. },
  2067. ],
  2068. autoReload: false,
  2069. GM_getValue,
  2070. GM_setValue,
  2071. GM_registerMenuCommand,
  2072. GM_unregisterMenuCommand,
  2073. });
  2074.  
  2075.  
  2076. // 获取某个菜单项的值
  2077. GM_Menu.get("menu_key");
  2078. > true
  2079.  
  2080. // 获取某个菜单项的开启/关闭后显示的文本
  2081. GM_Menu.getShowTextValue("menu_key");
  2082. > √测试按钮
  2083.  
  2084. // 添加键为menu_key2的菜单项
  2085. GM_Menu.add({
  2086. key:"menu_key2",
  2087. text: "测试按钮2",
  2088. enable: false,
  2089. showText(text,enable){
  2090. return "[" + (enable ? "√" : "×") + "]" + text;
  2091. },
  2092. callback(data){
  2093. console.log("点击菜单,值修改为",data.enable);
  2094. }
  2095. });
  2096. // 使用数组的方式添加多个菜单,如menu_key3、menu_key4
  2097. GM_Menu.add([
  2098. {
  2099. key:"menu_key3",
  2100. text: "测试按钮3",
  2101. enable: false,
  2102. showText(text,enable){
  2103. return "[" + (enable ? "√" : "×") + "]" + text;
  2104. },
  2105. callback(data){
  2106. console.log("点击菜单,值修改为",data.enable);
  2107. }
  2108. },
  2109. {
  2110. key:"menu_key4",
  2111. text: "测试按钮4",
  2112. enable: false,
  2113. showText(text,enable){
  2114. return "[" + (enable ? "√" : "×") + "]" + text;
  2115. },
  2116. callback(data){
  2117. console.log("点击菜单,值修改为",data.enable);
  2118. }
  2119. }
  2120. ]);
  2121.  
  2122. // 更新键为menu_key的显示文字和点击回调
  2123. GM_Menu.update({
  2124. menu_key:{
  2125. text: "更新后的测试按钮",
  2126. enable: true,
  2127. showText(text,enable){
  2128. return "[" + (enable ? "√" : "×") + "]" + text;
  2129. },
  2130. callback(data){
  2131. console.log("点击菜单更新后的测试按钮,新值修改为",data.enable);
  2132. }
  2133. }
  2134. });
  2135.  
  2136. // 删除键为menu_key的菜单
  2137. GM_Menu.delete("menu_key");
  2138. **/
  2139. Utils.GM_Menu = function (details) {
  2140. /* 配置数据 */
  2141. let data = details.data || [];
  2142. /* 获取存储的数据 */
  2143. let _GM_getValue_ = details.GM_getValue;
  2144. /* 设置数据到存储 */
  2145. let _GM_setValue_ = details.GM_setValue;
  2146. /* 注册菜单 */
  2147. let _GM_registerMenuCommand_ = details.GM_registerMenuCommand;
  2148. /* 卸载菜单 */
  2149. let _GM_unregisterMenuCommand_ = details.GM_unregisterMenuCommand;
  2150. if (typeof _GM_getValue_ !== "function") {
  2151. throw new Error(
  2152. "Utils.GM_Menu 请在脚本开头加上 @grant GM_getValue,且传入该对象"
  2153. );
  2154. }
  2155. if (typeof _GM_setValue_ !== "function") {
  2156. throw new Error(
  2157. "Utils.GM_Menu 请在脚本开头加上 @grant GM_setValue,且传入该对象"
  2158. );
  2159. }
  2160. if (typeof _GM_registerMenuCommand_ !== "function") {
  2161. throw new Error(
  2162. "Utils.GM_Menu 请在脚本开头加上 @grant GM_registerMenuCommand,且传入该对象"
  2163. );
  2164. }
  2165. if (typeof _GM_unregisterMenuCommand_ !== "function") {
  2166. throw new Error(
  2167. "Utils.GM_Menu 请在脚本开头加上 @grant GM_unregisterMenuCommand,且传入该对象"
  2168. );
  2169. }
  2170. let that = this;
  2171. /**
  2172. * 注册的菜单的映射信息
  2173. * @type {Map<number,GM_Menu_Details>}
  2174. */
  2175. let menuIdMap = new Map();
  2176.  
  2177. /**
  2178. * 本地存储的键名
  2179. */
  2180. let LocalStorage_Key_Name = "GM_Menu_Local_Map";
  2181. /**
  2182. * 菜单enable为true的emoji
  2183. */
  2184. let Enable_True_Emoji = "✅";
  2185. /**
  2186. * 菜单enable为false的emoji
  2187. */
  2188. let Enable_False_Emoji = "❌";
  2189. /* 自动刷新网页,默认为true */
  2190. let Default_AutoReload =
  2191. typeof details.autoReload === "boolean" ? details.autoReload : true;
  2192. /**
  2193. * 菜单isStoreValue的默认值
  2194. */
  2195. let Default_IsStoreValue = true;
  2196. /**
  2197. * 获取本地存储菜单键值
  2198. * @param {string} key 键
  2199. * @returns {boolean}
  2200. */
  2201. let getLocalMenuData = function (key, defaultValue) {
  2202. let localData = _GM_getValue_(LocalStorage_Key_Name, {});
  2203. return localData[key] == null ? defaultValue : localData[key];
  2204. };
  2205.  
  2206. /**
  2207. * 设置本地存储菜单键值
  2208. * @param {string} key 键
  2209. * @param {boolean} value 值
  2210. */
  2211. let setLocalMenuData = function (key, value) {
  2212. let localData = _GM_getValue_(LocalStorage_Key_Name, {});
  2213. localData[key] = value;
  2214. _GM_setValue_(LocalStorage_Key_Name, localData);
  2215. };
  2216.  
  2217. /**
  2218. * 对菜单数据进行处理
  2219. * @param { GM_Menu_Details } data
  2220. * @returns { any[] }
  2221. */
  2222. let handleMenuData = function (data) {
  2223. /* 本地存储的键名 */
  2224. let menuLocalDataItemKey = data.key;
  2225. /* 文本 */
  2226. let text = data.text;
  2227. /* 菜单默认开启的状态 */
  2228. let defaultEnable = Boolean(
  2229. getLocalMenuData(menuLocalDataItemKey, data.enable)
  2230. );
  2231. /* 油猴菜单上显示的文本 */
  2232. let showText = data.showText(text, defaultEnable);
  2233. /* 用户点击后的回调 */
  2234. let defaultClickCallBack = data.callback;
  2235.  
  2236. let menuOptions = {
  2237. /**
  2238. * 菜单的id
  2239. */
  2240. id: data.id,
  2241. /**
  2242. * 点击菜单项后是否应关闭弹出菜单
  2243. */
  2244. autoClose: data.autoClose,
  2245. /**
  2246. * 菜单项的可选访问键
  2247. */
  2248. accessKey: data.accessKey,
  2249. /**
  2250. * 菜单项的鼠标悬浮上的工具提示
  2251. */
  2252. title: data.title,
  2253. };
  2254. /* 点击菜单后触发callback后的网页是否刷新 */
  2255. data.autoReload =
  2256. typeof data.autoReload !== "boolean"
  2257. ? Default_AutoReload
  2258. : data.autoReload;
  2259. data.isStoreValue =
  2260. typeof data.isStoreValue !== "boolean"
  2261. ? Default_IsStoreValue
  2262. : data.isStoreValue;
  2263. /* 用户点击菜单后的回调函数 */
  2264. let clickCallBack = function (event) {
  2265. let localEnable = Boolean(
  2266. getLocalMenuData(menuLocalDataItemKey, defaultEnable)
  2267. );
  2268. if (data.isStoreValue) {
  2269. setLocalMenuData(menuLocalDataItemKey, !localEnable);
  2270. }
  2271. if (typeof defaultClickCallBack === "function") {
  2272. defaultClickCallBack({
  2273. key: menuLocalDataItemKey,
  2274. enable: !localEnable,
  2275. oldEnable: localEnable,
  2276. event: event,
  2277. storeValue(_value_) {
  2278. setLocalMenuData(menuLocalDataItemKey, _value_);
  2279. },
  2280. });
  2281. }
  2282. /* 不刷新网页就刷新菜单 */
  2283. if (data.autoReload) {
  2284. window.location.reload();
  2285. } else {
  2286. that.update();
  2287. }
  2288. };
  2289. let menuOptionsLength = Object.values(menuOptions).filter(
  2290. (_item_) => _item_ != null
  2291. ).length;
  2292.  
  2293. if (menuOptionsLength === 0) {
  2294. return [showText, clickCallBack];
  2295. } else if (menuOptionsLength === 1 && menuOptions.accessKey != null) {
  2296. return [showText, clickCallBack, menuOptions["accessKey"]];
  2297. } else {
  2298. /* 这个是版本 > 4.20.6186才会有的选项 */
  2299. return [showText, clickCallBack, menuOptions];
  2300. }
  2301. };
  2302.  
  2303. /**
  2304. * 处理初始化配置
  2305. * @param { GM_Menu_Details } _detail_
  2306. * @returns { GM_Menu_Details }
  2307. */
  2308. let handleInitDetail = function (_detail_) {
  2309. _detail_.enable = Boolean(
  2310. getLocalMenuData(_detail_.key, _detail_.enable)
  2311. );
  2312. if (typeof _detail_.showText !== "function") {
  2313. _detail_.showText = function (_text_, _enable_) {
  2314. return (
  2315. (_enable_ ? Enable_True_Emoji : Enable_False_Emoji) + " " + _text_
  2316. );
  2317. };
  2318. }
  2319. return _detail_;
  2320. };
  2321. /**
  2322. * 初始化数据
  2323. * @returns { GM_Menu_Details[] }
  2324. */
  2325. let init = function () {
  2326. menuIdMap.clear();
  2327. data.forEach((dataItem, dataIndex) => {
  2328. dataItem = handleInitDetail(dataItem);
  2329. data[dataIndex].enable = dataItem.enable;
  2330. });
  2331. };
  2332.  
  2333. /**
  2334. * 注册油猴菜单
  2335. * @param { ?GM_Menu_Config[] } _data_ 如果存在,使用它
  2336. */
  2337. let register = function (_data_) {
  2338. (_data_ || data).forEach((item) => {
  2339. let resultList = handleMenuData(item);
  2340. data.id = _GM_registerMenuCommand_(...resultList);
  2341. menuIdMap.set(data.id, item);
  2342. });
  2343. };
  2344.  
  2345. /**
  2346. * 获取目标菜单配置
  2347. * @param {string} menuKey 菜单-键key
  2348. * @returns {GM_Menu_Details}
  2349. */
  2350. let getTargetMenu = function (menuKey) {
  2351. return data.find((item) => item.key == menuKey);
  2352. };
  2353. /**
  2354. * 根据键值获取enable值
  2355. * @param {string} menuKey 菜单-键key
  2356. * @returns {boolean}
  2357. */
  2358. this.get = function (menuKey) {
  2359. return this.getEnable(menuKey);
  2360. };
  2361. /**
  2362. * 根据键值获取enable值
  2363. * @param {string} menuKey 菜单-键key
  2364. * @returns {boolean}
  2365. */
  2366. this.getEnable = function (menuKey) {
  2367. return getTargetMenu(menuKey).enable;
  2368. };
  2369. /**
  2370. * 根据键值获取text值
  2371. * @param {string} menuKey 菜单-键key
  2372. * @returns {string}
  2373. */
  2374. this.getText = function (menuKey) {
  2375. return getTargetMenu(menuKey).text;
  2376. };
  2377. /**
  2378. * 根据键值获取showText函数的值
  2379. * @param {string} menuKey 菜单-键key
  2380. * @returns {string}
  2381. */
  2382. this.getShowTextValue = function (menuKey) {
  2383. return getTargetMenu(menuKey).showText(
  2384. this.getText(menuKey),
  2385. this.get(menuKey)
  2386. );
  2387. };
  2388. /**
  2389. * 获取当前已注册菜单的id
  2390. * @param {string} menuKey
  2391. * @returns {?number}
  2392. */
  2393. this.getMenuId = function (menuKey) {
  2394. let result = null;
  2395. menuIdMap.forEach((value, key) => {
  2396. if (value.key === menuKey) {
  2397. result = key;
  2398. return;
  2399. }
  2400. });
  2401. return result;
  2402. };
  2403. /**
  2404. * 根据键值获取accessKey值
  2405. * @param {string} menuKey 菜单-键key
  2406. * @returns {?string}
  2407. */
  2408. this.getAccessKey = function (menuKey) {
  2409. return getTargetMenu(menuKey).accessKey;
  2410. };
  2411. /**
  2412. * 根据键值获取autoClose值
  2413. * @param {string} menuKey 菜单-键key
  2414. * @returns {?boolean}
  2415. */
  2416. this.getAutoClose = function (menuKey) {
  2417. return getTargetMenu(menuKey).autoClose;
  2418. };
  2419. /**
  2420. * 根据键值获取autoReload值
  2421. * @param {string} menuKey 菜单-键key
  2422. * @returns {boolean}
  2423. */
  2424. this.getAutoReload = function (menuKey) {
  2425. return getTargetMenu(menuKey).autoReload;
  2426. };
  2427. /**
  2428. * 根据键值获取callback函数
  2429. * @param {string} menuKey 菜单-键key
  2430. * @returns {Function|undefined}
  2431. */
  2432. this.getCallBack = function (menuKey) {
  2433. return getTargetMenu(menuKey).callback;
  2434. };
  2435. /**
  2436. * 获取当enable为true时默认显示在菜单中前面的emoji图标
  2437. * @returns {string}
  2438. */
  2439. this.getEnableTrueEmoji = function () {
  2440. return Enable_True_Emoji;
  2441. };
  2442. /**
  2443. * 获取当enable为false时默认显示在菜单中前面的emoji图标
  2444. * @returns {string}
  2445. */
  2446. this.getEnableFalseEmoji = function () {
  2447. return Enable_False_Emoji;
  2448. };
  2449. /**
  2450. * 获取本地存储的菜单外部的键名
  2451. * @param {string} keyName
  2452. */
  2453. this.getLocalStorageKeyName = function () {
  2454. return LocalStorage_Key_Name;
  2455. };
  2456. /**
  2457. * 设置菜单的值
  2458. * @param {string} menuKey 菜单-键key
  2459. * @param {any} value 需要设置的值
  2460. */
  2461. this.setValue = function (menuKey, value) {
  2462. setLocalMenuData(menuKey, value);
  2463. };
  2464. /**
  2465. * 设置菜单的值
  2466. * @param {string} menuKey 菜单-键key
  2467. * @param {boolean} value 需要设置的值
  2468. */
  2469. this.setEnable = function (menuKey, value) {
  2470. this.setValue(menuKey, Boolean(value));
  2471. };
  2472. /**
  2473. * 设置当enable为true时默认显示在菜单中前面的emoji图标
  2474. * @param {string} emojiString
  2475. */
  2476. this.setEnableTrueEmoji = function (emojiString) {
  2477. if (typeof emojiString !== "string") {
  2478. throw new Error("参数emojiString必须是string类型");
  2479. }
  2480. Enable_True_Emoji = emojiString;
  2481. };
  2482. /**
  2483. * 设置当enable为false时默认显示在菜单中前面的emoji图标
  2484. * @param {string} emojiString
  2485. */
  2486. this.setEnableFalseEmoji = function (emojiString) {
  2487. if (typeof emojiString !== "string") {
  2488. throw new Error("参数emojiString必须是string类型");
  2489. }
  2490. Enable_False_Emoji = emojiString;
  2491. };
  2492. /**
  2493. * 设置本地存储的菜单外部的键名
  2494. * @param {string} keyName
  2495. */
  2496. this.setLocalStorageKeyName = function (keyName) {
  2497. if (typeof keyName !== "string") {
  2498. throw new Error("参数keyName必须是string类型");
  2499. }
  2500. LocalStorage_Key_Name = keyName;
  2501. };
  2502. /**
  2503. * 新增菜单数据
  2504. * @param {GM_Menu_Details[]|GM_Menu_Details} paramData
  2505. */
  2506. this.add = function (paramData) {
  2507. if (Array.isArray(paramData)) {
  2508. data = data.concat(paramData);
  2509. } else {
  2510. data.push(paramData);
  2511. }
  2512. this.update();
  2513. };
  2514. /**
  2515. * 更新菜单数据
  2516. * @param { GM_Menu_Details[]|GM_Menu_Details|undefined } options 数据
  2517. */
  2518. this.update = function (options) {
  2519. let optionsList = [];
  2520. if (Array.isArray(options)) {
  2521. /* 是数组 */
  2522. optionsList = optionsList.concat(options);
  2523. } else if (options != null) {
  2524. /* 是单个配置 */
  2525. optionsList.push(options);
  2526. }
  2527. optionsList.forEach((item) => {
  2528. let targetMenu = getTargetMenu(item.key);
  2529. if (targetMenu) {
  2530. Object.assign(targetMenu, item);
  2531. }
  2532. });
  2533. menuIdMap.forEach((value, key) => {
  2534. this.delete(key);
  2535. });
  2536. init();
  2537. register();
  2538. };
  2539. /**
  2540. * 根据已注册菜单的id,来更新菜单配置,不会卸载菜单导致可能菜单选项可能会变化的情况
  2541. * @param { GM_Menu_Details[]|GM_Menu_Details|undefined } options 配置
  2542. */
  2543. this.updateOptionsWithId = function (options) {
  2544. /**
  2545. * @type {GM_Menu_Details[]}
  2546. */
  2547. let optionsList = [];
  2548. if (Array.isArray(options)) {
  2549. /* 是数组 */
  2550. optionsList = optionsList.concat(options);
  2551. } else if (options != null) {
  2552. /* 是单个配置 */
  2553. optionsList.push(options);
  2554. }
  2555. for (let option of optionsList) {
  2556. menuIdMap.forEach((value, key) => {
  2557. if (key === option.id) {
  2558. option = handleInitDetail(option);
  2559. let findDataIndex = data.findIndex(
  2560. (_item_) => _item_.key === value.key
  2561. );
  2562. if (findDataIndex !== -1) {
  2563. Object.assign(data[findDataIndex], option);
  2564. }
  2565. let resultList = handleMenuData(option);
  2566. _GM_registerMenuCommand_(...resultList);
  2567. menuIdMap.set(option.id, option);
  2568. }
  2569. });
  2570. }
  2571. };
  2572. /**
  2573. * 卸载菜单
  2574. * @param {number} menuId 已注册的菜单id
  2575. */
  2576. this.delete = function (menuId) {
  2577. _GM_unregisterMenuCommand_(menuId);
  2578. };
  2579.  
  2580. this.update();
  2581. };
  2582.  
  2583. /**
  2584. * 基于Function prototype,能够勾住和释放任何函数
  2585. *
  2586. * .hook
  2587. * + realFunc {string} 用于保存原始函数的函数名称,用于unHook
  2588. * + hookFunc {string} 替换的hook函数
  2589. * + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1
  2590. * + methodName {string} 匿名函数需显式传入目标函数名eg:this.Begin = function(){....};}
  2591. *
  2592. * .unhook
  2593. * + realFunc {string} 用于保存原始函数的函数名称,用于unHook
  2594. * + funcName {string} 被Hook的函数名称
  2595. * + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1
  2596. * @example
  2597. let hook = new Utils.Hooks();
  2598. hook.initEnv();
  2599. function myFunction(){
  2600. console.log("我自己需要执行的函数");
  2601. }
  2602. function testFunction(){
  2603. console.log("正常执行的函数");
  2604. }
  2605. testFunction.hook(testFunction,myFunction,window);
  2606. **/
  2607. Utils.Hooks = function () {
  2608. this.initEnv = function () {
  2609. Function.prototype.hook = function (realFunc, hookFunc, context) {
  2610. let _context = null; //函数上下文
  2611. let _funcName = null; //函数名
  2612.  
  2613. _context = context || window;
  2614. _funcName = getFuncName(this);
  2615. _context["realFunc_" + _funcName] = this;
  2616.  
  2617. if (
  2618. _context[_funcName].prototype &&
  2619. _context[_funcName].prototype.isHooked
  2620. ) {
  2621. console.log("Already has been hooked,unhook first");
  2622. return false;
  2623. }
  2624. function getFuncName(fn) {
  2625. // 获取函数名
  2626. let strFunc = fn.toString();
  2627. let _regex = /function\s+(\w+)\s*\(/;
  2628. let patten = strFunc.match(_regex);
  2629. if (patten) {
  2630. return patten[1];
  2631. }
  2632. return "";
  2633. }
  2634. try {
  2635. eval(
  2636. "_context[_funcName] = function " +
  2637. _funcName +
  2638. "(){\n" +
  2639. "let args = Array.prototype.slice.call(arguments,0);\n" +
  2640. "let obj = this;\n" +
  2641. "hookFunc.apply(obj,args);\n" +
  2642. "return _context['realFunc_" +
  2643. _funcName +
  2644. "'].apply(obj,args);\n" +
  2645. "};"
  2646. );
  2647. _context[_funcName].prototype.isHooked = true;
  2648. return true;
  2649. } catch (e) {
  2650. console.log("Hook failed,check the params.");
  2651. return false;
  2652. }
  2653. };
  2654. Function.prototype.unhook = function (realFunc, funcName, context) {
  2655. let _context = null;
  2656. let _funcName = null;
  2657. _context = context || window;
  2658. _funcName = funcName;
  2659. if (!_context[_funcName].prototype.isHooked) {
  2660. console.log("No function is hooked on");
  2661. return false;
  2662. }
  2663. _context[_funcName] = _context["realFunc" + _funcName];
  2664. delete _context["realFunc_" + _funcName];
  2665. return true;
  2666. };
  2667. };
  2668. this.cleanEnv = function () {
  2669. if (Function.prototype.hasOwnProperty("hook")) {
  2670. delete Function.prototype.hook;
  2671. }
  2672. if (Function.prototype.hasOwnProperty("unhook")) {
  2673. delete Function.prototype.unhook;
  2674. }
  2675. return true;
  2676. };
  2677. };
  2678.  
  2679. /**
  2680. * 为减少代码量和回调,把GM_xmlhttpRequest封装
  2681. * 文档地址: https://www.tampermonkey.net/documentation.php?ext=iikm
  2682. * 其中onloadstart、onprogress、onreadystatechange是回调形式,onabort、ontimeout、onerror可以设置全局回调函数
  2683. * @param {Function} _GM_xmlHttpRequest_ 油猴中的GM_xmlhttpRequest(必须)
  2684. * @example
  2685. let httpx = new Utils.Httpx(GM_xmlhttpRequest);
  2686. let postResp = await httpx.post({
  2687. url:url,
  2688. data:JSON.stringify({
  2689. test:1
  2690. }),
  2691. timeout: 5000
  2692. });
  2693. console.log(postResp);
  2694. > {
  2695. status: true,
  2696. data: {responseText: "...", response: xxx,...},
  2697. msg: "请求完毕",
  2698. type: "onload",
  2699. }
  2700.  
  2701. if(postResp === "onload" && postResp.status){
  2702. // onload
  2703. }else if(postResp === "ontimeout"){
  2704. // ontimeout
  2705. }
  2706. * @example
  2707. // 也可以先配置全局参数
  2708. let httpx = new Utils.Httpx(GM_xmlhttpRequest);
  2709. httpx.config({
  2710. timeout: 5000,
  2711. async: false,
  2712. responseType: "html",
  2713. redirect: "follow",
  2714. })
  2715. // 优先级为 默认details < 全局details < 单独的details
  2716. */
  2717. Utils.Httpx = function (_GM_xmlHttpRequest_) {
  2718. if (typeof _GM_xmlHttpRequest_ !== "function") {
  2719. throw new Error(
  2720. "Utils.Httpx 请先加入@grant GM_xmlhttpRequest在开头且传入该参数"
  2721. );
  2722. }
  2723. /**
  2724. * @type {HttpxDetails}
  2725. */
  2726. let defaultDetails = {
  2727. url: void 0,
  2728. timeout: 5000,
  2729. async: false,
  2730. responseType: void 0,
  2731. headers: void 0,
  2732. data: void 0,
  2733. redirect: void 0,
  2734. cookie: void 0,
  2735. binary: void 0,
  2736. nocache: void 0,
  2737. revalidate: void 0,
  2738. context: void 0,
  2739. overrideMimeType: void 0,
  2740. anonymous: void 0,
  2741. fetch: void 0,
  2742. user: void 0,
  2743. password: void 0,
  2744. onabort() {},
  2745. onerror() {},
  2746. ontimeout() {},
  2747. onloadstart() {},
  2748. onreadystatechange() {},
  2749. onprogress() {},
  2750. };
  2751. /**
  2752. * 输出请求配置
  2753. */
  2754. let LOG_DETAILS = false;
  2755. /**
  2756. * 发送请求
  2757. * @param {HttpxDetails} details
  2758. */
  2759. function request(details) {
  2760. if (LOG_DETAILS) {
  2761. console.log("Httpx请求配置👇", details);
  2762. }
  2763. _GM_xmlHttpRequest_(details);
  2764. }
  2765.  
  2766. /**
  2767. * 获取请求配置
  2768. * @param {"get"|"post"|"head"|"options"|"delete"|"put"} method 当前请求方法,默认get
  2769. * @param {object} resolve promise回调
  2770. * @param {HttpxDetails} details 请求配置
  2771. * @returns
  2772. */
  2773. function getRequestDefails(method, resolve, details) {
  2774. return {
  2775. url: details.url || defaultDetails.url,
  2776. method: method || "GET",
  2777. timeout: details.timeout || defaultDetails.timeout,
  2778. async: details.async || defaultDetails.async,
  2779. responseType: details.responseType || defaultDetails.responseType,
  2780. headers: details.headers || defaultDetails.headers,
  2781. data: details.data || defaultDetails.data,
  2782. redirect: details.redirect || defaultDetails.redirect,
  2783. cookie: details.cookie || defaultDetails.cookie,
  2784. binary: details.binary || defaultDetails.binary,
  2785. nocache: details.nocache || defaultDetails.nocache,
  2786. revalidate: details.revalidate || defaultDetails.revalidate,
  2787. context: details.context || defaultDetails.context,
  2788. overrideMimeType:
  2789. details.overrideMimeType || defaultDetails.overrideMimeType,
  2790. anonymous: details.anonymous || defaultDetails.anonymous,
  2791. fetch: details.fetch || defaultDetails.fetch,
  2792. user: details.user || defaultDetails.user,
  2793. password: details.password || defaultDetails.password,
  2794. onabort() {
  2795. onAbortCallBack(details, resolve, arguments);
  2796. },
  2797. onerror() {
  2798. onErrorCallBack(details, resolve, arguments);
  2799. },
  2800. onloadstart() {
  2801. onLoadStartCallBack(details, arguments);
  2802. },
  2803. onprogress() {
  2804. onProgressCallBack(details, arguments);
  2805. },
  2806. onreadystatechange() {
  2807. onReadyStateChangeCallBack(details, arguments);
  2808. },
  2809. ontimeout() {
  2810. onTimeoutCallBack(details, resolve, arguments);
  2811. },
  2812. onload() {
  2813. onLoadCallBack(details, resolve, arguments);
  2814. },
  2815. };
  2816. }
  2817. /**
  2818. * 处理发送请求的details,去除值为undefined、空function的值
  2819. * @param {HttpxDetails} details
  2820. * @returns {HttpxDetails}
  2821. */
  2822. function handleRequestDetails(details) {
  2823. Object.keys(details).forEach((keyName) => {
  2824. if (
  2825. details[keyName] == null ||
  2826. (details[keyName] instanceof Function &&
  2827. Utils.isNull(details[keyName]))
  2828. ) {
  2829. delete details[keyName];
  2830. return;
  2831. }
  2832. });
  2833. if (Utils.isNull(details.url)) {
  2834. throw Error(`Utils.Httpx 参数 url不符合要求: ${details.url}`);
  2835. }
  2836. /* method值统一大写,兼容Via */
  2837. details.method = details.method.toUpperCase();
  2838. return details;
  2839. }
  2840.  
  2841. /**
  2842. * onabort请求被取消-触发
  2843. * @param {HttpxDetails} details 配置
  2844. * @param {()=>void} resolve 回调
  2845. * @param {any[]} argumentsList 参数列表
  2846. */
  2847. function onAbortCallBack(details, resolve, argumentsList) {
  2848. if ("onabort" in details) {
  2849. details.onabort.apply(this, argumentsList);
  2850. } else if ("onabort" in defaultDetails) {
  2851. defaultDetails.onabort.apply(this, argumentsList);
  2852. }
  2853. resolve({
  2854. status: false,
  2855. data: [...argumentsList],
  2856. msg: "请求被取消",
  2857. type: "onabort",
  2858. });
  2859. }
  2860.  
  2861. /**
  2862. * onerror请求异常-触发
  2863. * @param {HttpxDetails} details 配置
  2864. * @param {()=>void} resolve 回调
  2865. * @param {any[]} argumentsList 响应的参数列表
  2866. */
  2867. function onErrorCallBack(details, resolve, argumentsList) {
  2868. if ("onerror" in details) {
  2869. details.onerror.apply(this, argumentsList);
  2870. } else if ("onerror" in defaultDetails) {
  2871. defaultDetails.onerror.apply(this, argumentsList);
  2872. }
  2873. let response = argumentsList;
  2874. if (response.length) {
  2875. response = response[0];
  2876. }
  2877. resolve({
  2878. status: false,
  2879. data: response,
  2880. details: details,
  2881. msg: "请求异常",
  2882. type: "onerror",
  2883. });
  2884. }
  2885. /**
  2886. * ontimeout请求超时-触发
  2887. * @param {HttpxDetails} details 配置
  2888. * @param {()=>void} resolve 回调
  2889. * @param {any[]} argumentsList 参数列表
  2890. */
  2891. function onTimeoutCallBack(details, resolve, argumentsList) {
  2892. if ("ontimeout" in details) {
  2893. details.ontimeout.apply(this, argumentsList);
  2894. } else if ("ontimeout" in defaultDetails) {
  2895. defaultDetails.ontimeout.apply(this, argumentsList);
  2896. }
  2897. resolve({
  2898. status: false,
  2899. data: [...argumentsList],
  2900. msg: "请求超时",
  2901. type: "ontimeout",
  2902. });
  2903. }
  2904.  
  2905. /**
  2906. * onloadstart请求开始-触发
  2907. * @param {HttpxDetails} details 配置
  2908. * @param {any[]} argumentsList 参数列表
  2909. */
  2910. function onLoadStartCallBack(details, argumentsList) {
  2911. if ("onloadstart" in details) {
  2912. details.onloadstart.apply(this, argumentsList);
  2913. } else if ("onloadstart" in defaultDetails) {
  2914. defaultDetails.onloadstart.apply(this, argumentsList);
  2915. }
  2916. }
  2917.  
  2918. /**
  2919. * onreadystatechange准备状态改变-触发
  2920. * @param {HttpxDetails} details 配置
  2921. * @param {any[]} argumentsList 参数列表
  2922. */
  2923. function onReadyStateChangeCallBack(details, argumentsList) {
  2924. if ("onreadystatechange" in details) {
  2925. details.onreadystatechange.apply(this, argumentsList);
  2926. } else if ("onreadystatechange" in defaultDetails) {
  2927. defaultDetails.onreadystatechange.apply(this, argumentsList);
  2928. }
  2929. }
  2930.  
  2931. /**
  2932. * onprogress上传进度-触发
  2933. * @param {HttpxDetails} details 配置
  2934. * @param {any[]} argumentsList 参数列表
  2935. */
  2936. function onProgressCallBack(details, argumentsList) {
  2937. if ("onprogress" in details) {
  2938. details.onprogress.apply(this, argumentsList);
  2939. } else if ("onprogress" in defaultDetails) {
  2940. defaultDetails.onprogress.apply(this, argumentsList);
  2941. }
  2942. }
  2943.  
  2944. /**
  2945. * onload加载完毕-触发
  2946. * @param {HttpxDetails} details 配置
  2947. * @param {()=>void} resolve 回调
  2948. * @param {any[]} argumentsList 参数列表
  2949. */
  2950. function onLoadCallBack(details, resolve, argumentsList) {
  2951. /* X浏览器会因为设置了responseType导致不返回responseText */
  2952. let response = argumentsList[0];
  2953. if (
  2954. details.responseType === "json" &&
  2955. Utils.isNull(response.responseText) &&
  2956. typeof response.response === "object"
  2957. ) {
  2958. Utils.tryCatch().run(() => {
  2959. response.responseText = JSON.stringify(response.response);
  2960. });
  2961. }
  2962. /* 状态码2xx都是成功的 */
  2963. if (Math.floor(response.status / 100) === 2) {
  2964. resolve({
  2965. status: true,
  2966. data: response,
  2967. details: details,
  2968. msg: "请求完毕",
  2969. type: "onload",
  2970. });
  2971. } else {
  2972. onErrorCallBack(details, resolve, argumentsList);
  2973. }
  2974. }
  2975.  
  2976. /**
  2977. * GET 请求
  2978. * @param {...HttpxDetails|string} arguments
  2979. * @returns {Promise< HttpxAsyncResult >}
  2980. */
  2981. this.get = async function () {
  2982. let details = {};
  2983. if (typeof arguments[0] === "string") {
  2984. details.url = arguments[0];
  2985. if (typeof arguments[1] === "object") {
  2986. details = arguments[1];
  2987. details.url = arguments[0];
  2988. }
  2989. } else {
  2990. details = arguments[0];
  2991. }
  2992. return new Promise((resolve) => {
  2993. let requestDetails = getRequestDefails("get", resolve, details);
  2994. delete requestDetails.onprogress;
  2995. requestDetails = handleRequestDetails(requestDetails);
  2996. request(requestDetails);
  2997. });
  2998. };
  2999. /**
  3000. * POST 请求
  3001. * @param {...HttpxDetails|string} arguments
  3002. * @returns {Promise< HttpxAsyncResult >}
  3003. */
  3004. this.post = async function () {
  3005. let details = {};
  3006. if (typeof arguments[0] === "string") {
  3007. details.url = arguments[0];
  3008. if (typeof arguments[1] === "object") {
  3009. details = arguments[1];
  3010. details.url = arguments[0];
  3011. }
  3012. } else {
  3013. details = arguments[0];
  3014. }
  3015. return new Promise((resolve) => {
  3016. let requestDetails = getRequestDefails("post", resolve, details);
  3017. requestDetails = handleRequestDetails(requestDetails);
  3018. request(requestDetails);
  3019. });
  3020. };
  3021. /**
  3022. * HEAD 请求
  3023. * @param {...HttpxDetails|string} arguments
  3024. * @returns {Promise< HttpxAsyncResult >}
  3025. */
  3026. this.head = async function () {
  3027. let details = {};
  3028. if (typeof arguments[0] === "string") {
  3029. details.url = arguments[0];
  3030. if (typeof arguments[1] === "object") {
  3031. details = arguments[1];
  3032. details.url = arguments[0];
  3033. }
  3034. } else {
  3035. details = arguments[0];
  3036. }
  3037. return new Promise((resolve) => {
  3038. let requestDetails = getRequestDefails("head", resolve, details);
  3039. delete requestDetails.onprogress;
  3040. requestDetails = handleRequestDetails(requestDetails);
  3041. request(requestDetails);
  3042. });
  3043. };
  3044.  
  3045. /**
  3046. * OPTIONS请求
  3047. * @param {...HttpxDetails|string} arguments
  3048. * @returns {Promise< HttpxAsyncResult >}
  3049. */
  3050. this.options = async function () {
  3051. let details = {};
  3052. if (typeof arguments[0] === "string") {
  3053. details.url = arguments[0];
  3054. if (typeof arguments[1] === "object") {
  3055. details = arguments[1];
  3056. details.url = arguments[0];
  3057. }
  3058. } else {
  3059. details = arguments[0];
  3060. }
  3061. return new Promise((resolve) => {
  3062. let requestDetails = getRequestDefails("options", resolve, details);
  3063. delete requestDetails.onprogress;
  3064. requestDetails = handleRequestDetails(requestDetails);
  3065. request(requestDetails);
  3066. });
  3067. };
  3068.  
  3069. /**
  3070. * DELETE请求
  3071. * @param {...HttpxDetails|string} arguments
  3072. * @returns {Promise< HttpxAsyncResult >}
  3073. */
  3074. this.delete = async function () {
  3075. let details = {};
  3076. if (typeof arguments[0] === "string") {
  3077. details.url = arguments[0];
  3078. if (typeof arguments[1] === "object") {
  3079. details = arguments[1];
  3080. details.url = arguments[0];
  3081. }
  3082. } else {
  3083. details = arguments[0];
  3084. }
  3085. return new Promise((resolve) => {
  3086. let requestDetails = getRequestDefails("delete", resolve, details);
  3087. delete requestDetails.onprogress;
  3088. requestDetails = handleRequestDetails(requestDetails);
  3089. request(requestDetails);
  3090. });
  3091. };
  3092.  
  3093. /**
  3094. * PUT请求
  3095. * @param {...HttpxDetails|string} arguments
  3096. * @returns {Promise< HttpxAsyncResult >}
  3097. */
  3098. this.put = async function () {
  3099. let details = {};
  3100. if (typeof arguments[0] === "string") {
  3101. details.url = arguments[0];
  3102. if (typeof arguments[1] === "object") {
  3103. details = arguments[1];
  3104. details.url = arguments[0];
  3105. }
  3106. } else {
  3107. details = arguments[0];
  3108. }
  3109. return new Promise((resolve) => {
  3110. let requestDetails = getRequestDefails("put", resolve, details);
  3111. requestDetails = handleRequestDetails(requestDetails);
  3112. request(requestDetails);
  3113. });
  3114. };
  3115.  
  3116. /**
  3117. * 覆盖当前配置
  3118. * @param {HttpxDetails} details
  3119. */
  3120. this.config = function (details = {}) {
  3121. if (Object.hasOwnProperty.call(details, "logDetails")) {
  3122. LOG_DETAILS = details["logDetails"];
  3123. }
  3124. defaultDetails = Utils.assign(defaultDetails, details);
  3125. };
  3126. };
  3127.  
  3128. /**
  3129. * 浏览器端的indexedDB操作封装
  3130. * @example
  3131. let db = new Utils.indexedDB('web_DB', 'nav_text')
  3132. let data = {name:'管理员', roleId: 1, type: 1};
  3133. db.save('list',data).then((resolve)=>{
  3134. console.log(resolve,'存储成功')
  3135. })
  3136.  
  3137. db.get('list').then((resolve)=>{
  3138. console.log(resolve,'查询成功')
  3139. })
  3140.  
  3141. db.getPaging('list',20,10).then((resolve)=>{
  3142. console.log(resolve,'查询分页偏移第20,一共10行成功');
  3143. })
  3144.  
  3145. db.delete('list').then(resolve=>{
  3146. console.log(resolve,'删除成功---->>>>>>name')
  3147. })
  3148.  
  3149. db.deleteAll().then(resolve=>{
  3150. console.log(resolve,'清除数据库---->>>>>>name')
  3151. })
  3152. * @param {string} [dbName="default_db"] 数据存储名
  3153. * @param {string} [storeName="default_form"] 表名
  3154. * @param {number} [dbVersion=1] indexDB的版本号
  3155. **/
  3156. Utils.indexedDB = function (
  3157. dbName = "default_db",
  3158. storeName = "default_form",
  3159. dbVersion = 1
  3160. ) {
  3161. this.dbName = dbName;
  3162. /* websql的版本号,由于ios的问题,版本号的写法不一样 */
  3163. this.slqVersion = "1";
  3164. this.dbVersion = dbVersion;
  3165. this.storeName = storeName;
  3166. /* 监听IndexDB */
  3167. this.indexedDB =
  3168. window.indexedDB ||
  3169. window.mozIndexedDB ||
  3170. window.webkitIndexedDB ||
  3171. window.msIndexedDB;
  3172. if (!this.indexedDB) {
  3173. alert("很抱歉,您的浏览器不支持indexedDB");
  3174. }
  3175. /* 缓存数据库,避免同一个页面重复创建和销毁 */
  3176. this.db = {};
  3177. this.store = null;
  3178. this.errorCode = {
  3179. /* 错误码 */
  3180. success: {
  3181. code: 200,
  3182. msg: "操作成功",
  3183. },
  3184. error: {
  3185. code: 401,
  3186. msg: "操作失败",
  3187. },
  3188. open: { code: 91001, msg: "打开数据库失败" },
  3189. save: { code: 91002, msg: "保存数据失败" },
  3190. get: { code: 91003, msg: "获取数据失败" },
  3191. delete: { code: 91004, msg: "删除数据失败" },
  3192. deleteAll: { code: 91005, msg: "清空数据库失败" },
  3193. };
  3194. let that = this;
  3195. /**
  3196. * 创建 “表”
  3197. * @param {string} dbName 表名
  3198. * @returns
  3199. */
  3200. this.createStore = function (dbName) {
  3201. let txn, store;
  3202. if (that.indexedDB) {
  3203. /* 如果是支持IndexDB的 */
  3204. txn = that.db[dbName].transaction(that.storeName, "readwrite");
  3205. /* IndexDB的读写权限 */
  3206. store = txn.objectStore(that.storeName);
  3207. }
  3208. return store;
  3209. };
  3210. /**
  3211. * 打开数据库
  3212. * @param {function} callback 回调
  3213. * @param {string} dbName 数据库名
  3214. */
  3215. this.open = function (callback, dbName) {
  3216. /* 打开数据库 */
  3217. if (that.indexedDB) {
  3218. /* 如果支持IndexDB */
  3219. if (!that.db[dbName]) {
  3220. /* 如果缓存中没有,则进行数据库的创建或打开,提高效率 */
  3221. let request = that.indexedDB.open(dbName, that.dbVersion);
  3222. request.onerror = function (e) {
  3223. callback({
  3224. code: that.errorCode.open.code,
  3225. msg: that.errorCode.open.msg,
  3226. error: e,
  3227. });
  3228. };
  3229. request.onsuccess = function (e) {
  3230. if (!that.db[dbName]) {
  3231. that.db[dbName] = e.target.result;
  3232. }
  3233. let store = that.createStore(dbName);
  3234. callback(store);
  3235. };
  3236. request.onupgradeneeded = function (e) {
  3237. that.db[dbName] = e.target.result;
  3238. let store = that.db[dbName].createObjectStore(that.storeName, {
  3239. keyPath: "key",
  3240. });
  3241. store.transaction.oncomplete = function (event) {
  3242. callback(store);
  3243. };
  3244. };
  3245. } else {
  3246. /* 如果缓存中已经打开了数据库,就直接使用 */
  3247. let store = that.createStore(dbName);
  3248. callback(store);
  3249. }
  3250. }
  3251. };
  3252. /**
  3253. * 保存数据到数据库
  3254. * @param {string} key 数据key
  3255. * @param {any} value 数据值
  3256. * @returns {Promise< {
  3257. * code: number,
  3258. * msg: string,
  3259. * success: boolean
  3260. * }>}
  3261. */
  3262. this.save = async function (key, value) {
  3263. if (that.indexedDB) {
  3264. return new Promise((resolve, reject) => {
  3265. let dbName = that.dbName;
  3266. let inData = {
  3267. key: key,
  3268. value: value,
  3269. };
  3270. that.open(function (result) {
  3271. let error = result.hasOwnProperty("error");
  3272. if (error) {
  3273. resolve(result);
  3274. } else {
  3275. let request = result.put(inData);
  3276. request.onsuccess = function (e) {
  3277. /* 保存成功有success 字段 */
  3278. resolve({
  3279. code: that.errorCode.success.code,
  3280. msg: that.errorCode.success.msg,
  3281. success: true,
  3282. });
  3283. };
  3284. request.onerror = function (e) {
  3285. resolve({
  3286. code: that.errorCode.save.code,
  3287. msg: that.errorCode.save.msg,
  3288. error: e,
  3289. });
  3290. };
  3291. }
  3292. }, dbName);
  3293. });
  3294. }
  3295. };
  3296. /**
  3297. * 根据key获取值
  3298. * @param {string} key 数据key
  3299. * @returns {Promise< {
  3300. * code: number,
  3301. * msg: string,
  3302. * data: [...any],
  3303. * success: true
  3304. * }| {
  3305. * code: number,
  3306. * msg: string,
  3307. * error: Error,
  3308. * result: any,
  3309. * } >}
  3310. */
  3311. this.get = async function (key) {
  3312. return new Promise((resolve, reject) => {
  3313. let dbName = that.dbName;
  3314. if (that.indexedDB) {
  3315. that.open(function (result) {
  3316. /* 判断返回的数据中是否有error字段 */
  3317. let error = result.hasOwnProperty("error");
  3318. if (error) {
  3319. reject({
  3320. code: that.errorCode.open.get,
  3321. msg: that.errorCode.get.msg,
  3322. error: error,
  3323. result: result,
  3324. });
  3325. } else {
  3326. let request = result.get(key);
  3327. request.onsuccess = function (e) {
  3328. let result = e.target.result;
  3329. let data = result ? result.value : void 0;
  3330. resolve({
  3331. code: data
  3332. ? that.errorCode.success.code
  3333. : that.errorCode.error.code,
  3334. msg: data
  3335. ? that.errorCode.success.msg
  3336. : that.errorCode.error.msg,
  3337. data: data || [],
  3338. success: true,
  3339. });
  3340. };
  3341. request.onerror = function (e) {
  3342. reject({
  3343. code: that.errorCode.get.code,
  3344. msg: that.errorCode.get.msg,
  3345. result: result,
  3346. error: e,
  3347. });
  3348. };
  3349. }
  3350. }, dbName);
  3351. }
  3352. });
  3353. };
  3354. /**
  3355. * 正则获取数据
  3356. * @param {string} key 数据键
  3357. * @returns { Promise<{
  3358. * code: number,
  3359. * msg: string,
  3360. * data: [...any],
  3361. * success: true
  3362. * }|{
  3363. * code: number,
  3364. * msg: string,
  3365. * error: Error,
  3366. * result: any,
  3367. * }> }
  3368. */
  3369. this.regexpGet = async function (key) {
  3370. let list = [];
  3371. return new Promise((resolve, reject) => {
  3372. /* 正则查询 */
  3373. let dbName = that.dbName;
  3374. if (that.indexedDB) {
  3375. that.open(function (result) {
  3376. /* 判断返回的数据中是否有error字段 */
  3377. let error = result.hasOwnProperty("error");
  3378. if (error) {
  3379. reject({
  3380. code: that.errorCode.open.get,
  3381. msg: that.errorCode.get.msg,
  3382. error: error,
  3383. result: result,
  3384. });
  3385. } else {
  3386. let request = result.getAll();
  3387. request.onsuccess = function (e) {
  3388. let result = e.target.result;
  3389. if (result.length !== 0) {
  3390. result.forEach((item, index) => {
  3391. if (item["key"].match(key)) {
  3392. let concatList = item["value"];
  3393. concatList["key"] = item["key"];
  3394. list = [...list, concatList];
  3395. }
  3396. });
  3397. }
  3398. resolve({
  3399. code: that.errorCode.success.code,
  3400. msg: that.errorCode.success.msg,
  3401. data: list,
  3402. success: true,
  3403. });
  3404. };
  3405. request.onerror = function (e) {
  3406. reject({
  3407. code: that.errorCode.get.code,
  3408. msg: that.errorCode.get.msg,
  3409. result: result,
  3410. error: e,
  3411. });
  3412. };
  3413. }
  3414. }, dbName);
  3415. }
  3416. });
  3417. };
  3418. /**
  3419. * 删除数据
  3420. * @param {string} key 数据键
  3421. * @returns {Promise<{
  3422. * code: number,
  3423. * msg: string,
  3424. * success: true,
  3425. * }|{
  3426. * code: number,
  3427. * msg: string,
  3428. * error: Error,
  3429. * }>}
  3430. */
  3431. this.delete = async function (key) {
  3432. return new Promise((resolve, reject) => {
  3433. /* 根据key删除某条数据 */
  3434. let dbName = that.dbName;
  3435. if (that.indexedDB) {
  3436. that.open(function (result) {
  3437. let error = result.hasOwnProperty("error");
  3438. if (error) {
  3439. resolve(result);
  3440. } else {
  3441. let request = result.get(key);
  3442. request.onsuccess = function (e) {
  3443. let recode = e.target.result;
  3444. if (recode) {
  3445. request = result.delete(key);
  3446. }
  3447. resolve({
  3448. code: recode
  3449. ? that.errorCode.success.code
  3450. : that.errorCode.error.code,
  3451. msg: recode
  3452. ? that.errorCode.success.msg
  3453. : that.errorCode.error.msg,
  3454. success: true,
  3455. });
  3456. };
  3457. request.onerror = function (e) {
  3458. resolve({
  3459. code: that.errorCode.delete.code,
  3460. msg: that.errorCode.delete.msg,
  3461. error: e,
  3462. });
  3463. };
  3464. }
  3465. }, dbName);
  3466. }
  3467. });
  3468. };
  3469. /**
  3470. * 删除所有数据
  3471. * @returns {Promise<{
  3472. * code: number,
  3473. * msg: string,
  3474. * error: Error,
  3475. * result: any,
  3476. * }|{
  3477. * code: number,
  3478. * msg: string,
  3479. * success: true,
  3480. * }>}
  3481. */
  3482. this.deleteAll = async function () {
  3483. return new Promise((resolve, reject) => {
  3484. /* 清空数据库 */
  3485. let dbName = that.dbName;
  3486. if (that.indexedDB) {
  3487. that.open(function (result) {
  3488. let error = result.hasOwnProperty("error");
  3489. if (error) {
  3490. resolve({
  3491. code: that.errorCode.deleteAll.code,
  3492. msg: that.errorCode.deleteAll.msg,
  3493. error: error,
  3494. result: result,
  3495. });
  3496. } else {
  3497. result.clear();
  3498. resolve({
  3499. code: that.errorCode.success.code,
  3500. msg: that.errorCode.success.msg,
  3501. success: true,
  3502. });
  3503. }
  3504. }, dbName);
  3505. }
  3506. });
  3507. };
  3508. };
  3509.  
  3510. /**
  3511. * 判断目标函数是否是Native Code
  3512. * @param {function} target
  3513. * @returns {boolean}
  3514. * + true 是Native
  3515. * + false 不是Native
  3516. * @example
  3517. * Utils.isNativeFunc(window.location.assign)
  3518. * > true
  3519. */
  3520. Utils.isNativeFunc = function (target) {
  3521. return Boolean(
  3522. target.toString().match(/^function .*\(\) { \[native code\] }$/)
  3523. );
  3524. };
  3525.  
  3526. /**
  3527. * 判断当前的位置是否位于页面底部附近
  3528. * @param {number} [nearValue=50] 判断在页面底部的误差值,默认:50
  3529. * @returns {boolean}
  3530. * + true 在底部附近
  3531. * + false 不在底部附近
  3532. */
  3533. Utils.isNearBottom = function (nearValue = 50) {
  3534. var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  3535. var windowHeight =
  3536. window.innerHeight || document.documentElement.clientHeight;
  3537. var documentHeight = document.documentElement.scrollHeight;
  3538. return scrollTop + windowHeight >= documentHeight - nearValue;
  3539. };
  3540.  
  3541. /**
  3542. * 判断对象是否是元素
  3543. * @param {any} target
  3544. * @returns {boolean}
  3545. * + true 是元素
  3546. * + false 不是元素
  3547. * @example
  3548. * Utils.isDOM(document.querySelector("a"))
  3549. * > true
  3550. */
  3551. Utils.isDOM = function (target) {
  3552. return target instanceof Node;
  3553. };
  3554.  
  3555. /**
  3556. * 判断浏览器是否支持全屏
  3557. * @returns {boolean}
  3558. */
  3559. Utils.isFullscreenEnabled = function () {
  3560. return !!(
  3561. document.fullscreenEnabled ||
  3562. document.webkitFullScreenEnabled ||
  3563. document.mozFullScreenEnabled ||
  3564. document.msFullScreenEnabled
  3565. );
  3566. };
  3567. /**
  3568. * 判断对象是否是jQuery对象
  3569. * @param {any} target
  3570. * @returns {boolean}
  3571. * + true 是jQuery对象
  3572. * + false 不是jQuery对象
  3573. * @example
  3574. * Utils.isJQuery($("a"))
  3575. * > true
  3576. */
  3577. Utils.isJQuery = function (target) {
  3578. let result = false;
  3579. if (typeof jQuery === "object" && target instanceof jQuery) {
  3580. result = true;
  3581. }
  3582. if (target == null) {
  3583. return false;
  3584. }
  3585. if (typeof target === "object") {
  3586. /* 也有种可能,这个jQuery对象是1.8.3版本的,页面中的jQuery是3.4.1版本的 */
  3587. let jQueryProps = [
  3588. "add",
  3589. "addBack",
  3590. "addClass",
  3591. "after",
  3592. "ajaxComplete",
  3593. "ajaxError",
  3594. "ajaxSend",
  3595. "ajaxStart",
  3596. "ajaxStop",
  3597. "ajaxSuccess",
  3598. "animate",
  3599. "append",
  3600. "appendTo",
  3601. "attr",
  3602. "before",
  3603. "bind",
  3604. "blur",
  3605. "change",
  3606. "children",
  3607. "clearQueue",
  3608. "click",
  3609. "clone",
  3610. "closest",
  3611. "constructor",
  3612. "contents",
  3613. "contextmenu",
  3614. "css",
  3615. "data",
  3616. "dblclick",
  3617. "delay",
  3618. "delegate",
  3619. "dequeue",
  3620. "each",
  3621. "empty",
  3622. "end",
  3623. "eq",
  3624. "extend",
  3625. "fadeIn",
  3626. "fadeOut",
  3627. "fadeTo",
  3628. "fadeToggle",
  3629. "filter",
  3630. "find",
  3631. "first",
  3632. "focus",
  3633. "focusin",
  3634. "focusout",
  3635. "get",
  3636. "has",
  3637. "hasClass",
  3638. "height",
  3639. "hide",
  3640. "hover",
  3641. "html",
  3642. "index",
  3643. "init",
  3644. "innerHeight",
  3645. "innerWidth",
  3646. "insertAfter",
  3647. "insertBefore",
  3648. "is",
  3649. "jquery",
  3650. "keydown",
  3651. "keypress",
  3652. "keyup",
  3653. "last",
  3654. "load",
  3655. "map",
  3656. "mousedown",
  3657. "mouseenter",
  3658. "mouseleave",
  3659. "mousemove",
  3660. "mouseout",
  3661. "mouseover",
  3662. "mouseup",
  3663. "next",
  3664. "nextAll",
  3665. "not",
  3666. "off",
  3667. "offset",
  3668. "offsetParent",
  3669. "on",
  3670. "one",
  3671. "outerHeight",
  3672. "outerWidth",
  3673. "parent",
  3674. "parents",
  3675. "position",
  3676. "prepend",
  3677. "prependTo",
  3678. "prev",
  3679. "prevAll",
  3680. "prevUntil",
  3681. "promise",
  3682. "prop",
  3683. "pushStack",
  3684. "queue",
  3685. "ready",
  3686. "remove",
  3687. "removeAttr",
  3688. "removeClass",
  3689. "removeData",
  3690. "removeProp",
  3691. "replaceAll",
  3692. "replaceWith",
  3693. "resize",
  3694. "scroll",
  3695. "scrollLeft",
  3696. "scrollTop",
  3697. "select",
  3698. "show",
  3699. "siblings",
  3700. "slice",
  3701. "slideDown",
  3702. "slideToggle",
  3703. "slideUp",
  3704. "sort",
  3705. "splice",
  3706. "text",
  3707. "toArray",
  3708. "toggle",
  3709. "toggleClass",
  3710. "trigger",
  3711. "triggerHandler",
  3712. "unbind",
  3713. "width",
  3714. "wrap",
  3715. ];
  3716. for (const jQueryPropsName of jQueryProps) {
  3717. if (!(jQueryPropsName in target)) {
  3718. result = false;
  3719. /* console.log(jQueryPropsName); */
  3720. break;
  3721. } else {
  3722. result = true;
  3723. }
  3724. }
  3725. }
  3726. return result;
  3727. };
  3728.  
  3729. /**
  3730. * 判断当前设备是否是移动端
  3731. * @param {string} [userAgent=navigator.userAgent] UA字符串,默认使用当前的navigator.userAgent
  3732. * @returns {boolean}
  3733. * + true 是移动端
  3734. * + false 不是移动端
  3735. * @example
  3736. * Utils.isPhone();
  3737. * > true
  3738. **/
  3739. Utils.isPhone = function (userAgent = navigator.userAgent) {
  3740. return Boolean(/(iPhone|iPad|iPod|iOS|Android|Mobile)/i.test(userAgent));
  3741. };
  3742.  
  3743. /**
  3744. * 判断对象是否不为空
  3745. * @param {any} obj
  3746. * @returns {boolean}
  3747. * + true 不为空
  3748. * + false 为空
  3749. * @example
  3750. * Utils.isNotNull("123");
  3751. * > true
  3752. */
  3753. Utils.isNotNull = function () {
  3754. return !Utils.isNull.apply(this, arguments);
  3755. };
  3756.  
  3757. /**
  3758. * 判断对象或数据是否为空
  3759. * String类型,如 ""、"null"、"undefined"、" "
  3760. * Number类型,如 0
  3761. * Object类型,如 {}
  3762. * @param {arguments} obj 需要判断的变量
  3763. * @returns {boolean}
  3764. * + true 为空
  3765. * + false 不为空
  3766. * @example
  3767. Utils.isNull({});
  3768. > true
  3769. * @example
  3770. Utils.isNull([]);
  3771. > true
  3772. * @example
  3773. Utils.isNull(" ");
  3774. > true
  3775. * @example
  3776. Utils.isNull(function(){});
  3777. > true
  3778. * @example
  3779. Utils.isNull(()=>{}));
  3780. > true
  3781. * @example
  3782. Utils.isNull("undefined");
  3783. > true
  3784. * @example
  3785. Utils.isNull("null");
  3786. > true
  3787. * @example
  3788. Utils.isNull(" ", false);
  3789. > true
  3790. * @example
  3791. Utils.isNull([1],[]);
  3792. > false
  3793. * @example
  3794. Utils.isNull([],[1]);
  3795. > false
  3796. * @example
  3797. Utils.isNull(false,[123]);
  3798. > false
  3799. **/
  3800. Utils.isNull = function () {
  3801. let result = true;
  3802. let checkList = [...arguments];
  3803. for (const objItem of checkList) {
  3804. let itemResult = false;
  3805. switch (typeof objItem) {
  3806. case "undefined":
  3807. case "null":
  3808. itemResult = true;
  3809. break;
  3810. case "object":
  3811. /* object类型的也可能是null */
  3812. if (objItem == null) {
  3813. itemResult = true;
  3814. } else if (
  3815. Array.isArray(objItem) ||
  3816. objItem instanceof NodeList ||
  3817. objItem instanceof FileList
  3818. ) {
  3819. itemResult = objItem.length === 0;
  3820. } else if (objItem instanceof Map || objItem instanceof Set) {
  3821. itemResult = objItem.size === 0;
  3822. } else {
  3823. itemResult = Object.keys(objItem).length === 0;
  3824. }
  3825. break;
  3826. case "number":
  3827. itemResult = objItem === 0;
  3828. break;
  3829. case "string":
  3830. itemResult =
  3831. objItem.trim() === "" ||
  3832. objItem === "null" ||
  3833. objItem === "undefined";
  3834. break;
  3835. case "boolean":
  3836. itemResult = !objItem;
  3837. break;
  3838. case "function":
  3839. let funcStr = objItem.toString().replace(/\s/g, "");
  3840. /* 排除()=>{}、(xxx="")=>{}、function(){}、function(xxx=""){}、 */
  3841. itemResult = Boolean(
  3842. funcStr.match(/^\(.*?\)=>\{\}$|^function.*?\(.*?\)\{\}$/)
  3843. );
  3844. break;
  3845. }
  3846. result = result && itemResult;
  3847. }
  3848.  
  3849. return result;
  3850. };
  3851.  
  3852. /**
  3853. * 判断元素是否在页面中可见
  3854. * @param {[...HTMLElement]|NodeList} element 需要检查的元素,可以是普通元素|数组形式的元素|通过querySelectorAll获取的元素数组
  3855. * @param {boolean} [inView=false]
  3856. * + true 在窗口可视区域
  3857. * + false 不在窗口可视区域
  3858. * @returns {boolean}
  3859. * + true 可见
  3860. * + false 不可见
  3861. * @example
  3862. * Utils.isVisible(document.documentElement)
  3863. * > true
  3864. */
  3865. Utils.isVisible = function (element, inView = false) {
  3866. let needCheckDomList = [];
  3867. if (element instanceof Array || element instanceof NodeList) {
  3868. needCheckDomList = [...element];
  3869. } else {
  3870. needCheckDomList = [element];
  3871. }
  3872. let result = true;
  3873. for (const domItem of needCheckDomList) {
  3874. let domDisplay = window.getComputedStyle(domItem);
  3875. if (domDisplay.display === "none") {
  3876. result = false;
  3877. } else {
  3878. let domClientRect = domItem.getBoundingClientRect();
  3879. if (inView) {
  3880. let viewportWidth =
  3881. window.innerWidth || document.documentElement.clientWidth;
  3882. let viewportHeight =
  3883. window.innerHeight || document.documentElement.clientHeight;
  3884. result = !(
  3885. domClientRect.right < 0 ||
  3886. domClientRect.left > viewportWidth ||
  3887. domClientRect.bottom < 0 ||
  3888. domClientRect.top > viewportHeight
  3889. );
  3890. } else {
  3891. result = !(
  3892. domClientRect.bottom === 0 &&
  3893. domClientRect.height === 0 &&
  3894. domClientRect.left === 0 &&
  3895. domClientRect.right === 0 &&
  3896. domClientRect.top === 0 &&
  3897. domClientRect.width === 0 &&
  3898. domClientRect.x === 0 &&
  3899. domClientRect.y === 0
  3900. );
  3901. }
  3902. }
  3903. if (!result) {
  3904. /* 有一个不可见就退出循环 */
  3905. break;
  3906. }
  3907. }
  3908. return result;
  3909. };
  3910.  
  3911. /**
  3912. * 判断是否是Via浏览器环境
  3913. * @returns {boolean}
  3914. * + true 是Via
  3915. * + false 不是Via
  3916. * @example
  3917. * Utils.isWebView_Via()
  3918. * > false
  3919. */
  3920. Utils.isWebView_Via = function () {
  3921. let result = true;
  3922. if (typeof top.window.via === "object") {
  3923. for (const key in Object.values(top.window.via)) {
  3924. if (Object.hasOwnProperty.call(top.window.via, key)) {
  3925. let objValueFunc = top.window.via[key];
  3926. if (
  3927. typeof objValueFunc === "function" &&
  3928. Utils.isNativeFunc(objValueFunc)
  3929. ) {
  3930. result = true;
  3931. } else {
  3932. result = false;
  3933. break;
  3934. }
  3935. }
  3936. }
  3937. } else {
  3938. result = false;
  3939. }
  3940. return result;
  3941. };
  3942.  
  3943. /**
  3944. * 判断是否是X浏览器环境
  3945. * @returns {boolean}
  3946. * + true 是X浏览器
  3947. * + false 不是X浏览器
  3948. * @example
  3949. * Utils.isWebView_X()
  3950. * > false
  3951. */
  3952. Utils.isWebView_X = function () {
  3953. let result = true;
  3954. if (typeof top.window.mbrowser === "object") {
  3955. for (const key in Object.values(top.window.mbrowser)) {
  3956. if (Object.hasOwnProperty.call(top.window.mbrowser, key)) {
  3957. let objValueFunc = top.window.mbrowser[key];
  3958. if (
  3959. typeof objValueFunc === "function" &&
  3960. Utils.isNativeFunc(objValueFunc)
  3961. ) {
  3962. result = true;
  3963. } else {
  3964. result = false;
  3965. break;
  3966. }
  3967. }
  3968. }
  3969. } else {
  3970. result = false;
  3971. }
  3972. return result;
  3973. };
  3974.  
  3975. /**
  3976. * 把对象内的value值全部取出成数组
  3977. * @param {object} target 目标对象
  3978. * @returns {object} 返回数组
  3979. * @example
  3980. * Utils.parseObjectToArray({"工具类":"jsonToArray","return","Array"});
  3981. * > ['jsonToArray', 'Array']
  3982. **/
  3983. Utils.parseObjectToArray = function (target) {
  3984. if (typeof target !== "object") {
  3985. throw new Error(
  3986. "Utils.parseObjectToArray 参数 target 必须为 object 类型"
  3987. );
  3988. }
  3989. let result = [];
  3990. Object.keys(target).forEach(function (keyName) {
  3991. result = result.concat(target[keyName]);
  3992. });
  3993. return result;
  3994. };
  3995.  
  3996. /**
  3997. * 监听某个元素键盘按键事件或window全局按键事件
  3998. * 按下有值的键时触发,按下Ctrl\Alt\Shift\Meta是无值键。按下先触发keydown事件,再触发keypress事件。
  3999. * @param {Window|Node|HTMLElement|globalThis} target 需要监听的对象,可以是全局Window或者某个元素
  4000. * @param {"keyup"|"keypress"|"keydown"} eventName 事件名,默认keypress
  4001. * @param {?(keyName: string, keyValue:string,otherCodeList: string[], event: KeyboardEvent)=>{}} callback 自己定义的回调事件,参数1为当前的key,参数2为组合按键,数组类型,包含ctrl、shift、alt和meta(win键或mac的cmd键)
  4002. * @example
  4003. Utils.listenKeyboard(window,(keyName,keyValue,otherKey,event)=>{
  4004. if(keyName === "Enter"){
  4005. console.log("回车按键的值是:"+keyValue)
  4006. }
  4007. if(otherKey.indexOf("ctrl") && keyName === "Enter" ){
  4008. console.log("Ctrl和回车键");
  4009. }
  4010. })
  4011. * @example
  4012. 字母和数字键的键码值(keyCode)
  4013. 按键 键码 按键 键码 按键 键码 按键 键码
  4014. A 65 J 74 S 83 1 49
  4015. B 66 K 75 T 84 2 50
  4016. C 67 L 76 U 85 3 51
  4017. D 68 M 77 V 86 4 52
  4018. E 69 N 78 W 87 5 53
  4019. F 70 O 79 X 88 6 54
  4020. G 71 P 80 Y 89 7 55
  4021. H 72 Q 81 Z 90 8 56
  4022. I 73 R 82 0 48 9 57
  4023.  
  4024. 数字键盘上的键的键码值(keyCode)
  4025. 功能键键码值(keyCode)
  4026. 按键 键码 按键 键码 按键 键码 按键 键码
  4027. 0 96 8 104 F1 112 F7 118
  4028. 1 97 9 105 F2 113 F8 119
  4029. 2 98 * 106 F3 114 F9 120
  4030. 3 99 + 107 F4 115 F10 121
  4031. 4 100 Enter 108 F5 116 F11 122
  4032. 5 101 - 109 F6 117 F12 123
  4033. 6 102 . 110
  4034. 7 103 / 111
  4035. 控制键键码值(keyCode)
  4036. 按键 键码 按键 键码 按键 键码 按键 键码
  4037. BackSpace 8 Esc 27 → 39 -_ 189
  4038. Tab 9 Spacebar 32 ↓ 40 .> 190
  4039. Clear 12 Page Up 33 Insert 45 /? 191
  4040. Enter 13 Page Down 34 Delete 46 `~ 192
  4041. Shift 16 End 35 Num Lock 144 [{ 219
  4042. Control 17 Home 36 ;: 186 \| 220
  4043. Alt 18 ← 37 =+ 187 ]} 221
  4044. Cape Lock 20 ↑ 38 ,< 188 '" 222
  4045.  
  4046. 多媒体键码值(keyCode)
  4047. 按键 键码
  4048. 音量加 175
  4049. 音量减 174
  4050. 停止 179
  4051. 静音 173
  4052. 浏览器 172
  4053. 邮件 180
  4054. 搜索 170
  4055. 收藏 171
  4056. **/
  4057. Utils.listenKeyboard = function (target, eventName = "keypress", callback) {
  4058. if (
  4059. typeof target !== "object" ||
  4060. (typeof target["addEventListener"] !== "function" &&
  4061. typeof target["removeEventListener"] !== "function")
  4062. ) {
  4063. throw new Error(
  4064. "Utils.listenKeyboard 参数 target 必须为 Window|HTMLElement 类型"
  4065. );
  4066. }
  4067. let keyEvent = function (event) {
  4068. let keyName = event.key || event.code;
  4069. let keyValue = event.charCode || event.keyCode || event.which;
  4070. let otherCodeList = [];
  4071. if (event.ctrlKey) {
  4072. otherCodeList.push("ctrl");
  4073. }
  4074. if (event.altKey) {
  4075. otherCodeList.push("alt");
  4076. }
  4077. if (event.metaKey) {
  4078. otherCodeList.push("meta");
  4079. }
  4080. if (event.shiftKey) {
  4081. otherCodeList.push("shift");
  4082. }
  4083. if (typeof callback === "function") {
  4084. callback(keyName, keyValue, otherCodeList, event);
  4085. }
  4086. };
  4087. target.addEventListener(eventName, keyEvent);
  4088. return {
  4089. removeListen() {
  4090. target.removeEventListener(eventName, keyEvent);
  4091. },
  4092. };
  4093. };
  4094.  
  4095. /**
  4096. * 自动锁对象,用于循环判断运行的函数,在循环外new后使用,注意,如果函数内部存在异步操作,需要使用await
  4097. * @param {function|string} func 需要执行的函数
  4098. * @param {function|undefined} scope 函数作用域
  4099. * @param {number} [unLockDelayTime=0] 延迟xx毫秒后解锁,默认0
  4100. * @example
  4101. let lock = new Utils.LockFunction(()=>{console.log(1)}))
  4102. lock.run();
  4103. > 1
  4104. * @example
  4105. let lock = new Utils.LockFunction(()=>{console.log(1)}),true) -- 异步操作
  4106. await lock.run();
  4107. > 1
  4108. **/
  4109. Utils.LockFunction = function (func, scope, unLockDelayTime = 0) {
  4110. let flag = false;
  4111. let that = this;
  4112. scope = scope || this;
  4113. /**
  4114. * 锁
  4115. */
  4116. this.lock = function () {
  4117. flag = true;
  4118. };
  4119. /**
  4120. * 解锁
  4121. */
  4122. this.unlock = function () {
  4123. setTimeout(() => {
  4124. flag = false;
  4125. }, unLockDelayTime);
  4126. };
  4127. /**
  4128. * 执行
  4129. * @param {...any} funArgs 参数
  4130. */
  4131. this.run = async function (...funArgs) {
  4132. if (flag) {
  4133. return;
  4134. }
  4135. that.lock();
  4136. await func.apply(scope, funArgs); /* arguments调用 */
  4137. that.unlock();
  4138. };
  4139. };
  4140.  
  4141. /**
  4142. * @typedef {object} UtilsLogDetails
  4143. * @property {boolean} [tag=true] 是否输出Tag,false的话其它的颜色也不输出,默认为true
  4144. * @property {string} [successColor="#0000FF"] log.success的颜色,默认#0000FF
  4145. * @property {string} [warnColor="0"] log.warn的颜色,默认0
  4146. * @property {string} [errorColor="#FF0000"] log.error的颜色,默认#FF0000
  4147. * @property {string} [infoColor="0"] log.info的颜色,默认0
  4148. * @property {boolean} [debug=false] 是否开启debug模式,true会在控制台每次调用时输出调用函数的所在位置,false不会输出位置,默认false
  4149. * @property {boolean} [autoClearConsole=false] 当console输出超过logMaxCount数量自动清理控制台,默认false
  4150. * @property {boolean} [logMaxCount=999] console输出的最高数量,autoClearConsole开启则生效,默认999
  4151. */
  4152. /**
  4153. * 日志对象
  4154. * @param {object|undefined} _GM_info_ 油猴管理器的API GM_info,或者是一个对象,如{"script":{name:"Utils.Log"}}
  4155. * @example
  4156. let log = new Utils.Log(GM_info);
  4157. log.info("普通输出");
  4158. > 普通输出
  4159.  
  4160. log.success("成功输出");
  4161. > 成功输出
  4162.  
  4163. log.error("错误输出");
  4164. > 错误输出
  4165. log.warn("警告输出");
  4166. > 警告输出
  4167.  
  4168. log.tag = "自定义tag信息";
  4169. log.info("自定义info的颜色","#e0e0e0");
  4170. > 自定义info的颜色
  4171.  
  4172. log.config({
  4173. successColor: "#31dc02",
  4174. errorColor: "#e02d2d",
  4175. infoColor: "black",
  4176. })
  4177. log.success("颜色为#31dc02");
  4178. > 颜色为#31dc02
  4179. */
  4180. Utils.Log = function (
  4181. _GM_info_ = {
  4182. script: {
  4183. name: "Utils.Log",
  4184. },
  4185. }
  4186. ) {
  4187. let msgColorDetails = [
  4188. "font-weight: bold; color: cornflowerblue",
  4189. "font-weight: bold; color: cornflowerblue",
  4190. "font-weight: bold; color: darkorange",
  4191. "font-weight: bold; color: cornflowerblue",
  4192. ];
  4193. /**
  4194. * @type {UtilsLogDetails}
  4195. */
  4196. let details = {
  4197. tag: true,
  4198. successColor: "#0000FF",
  4199. errorColor: "#FF0000",
  4200. infoColor: "0",
  4201. warnColor: "0",
  4202. debug: false,
  4203. autoClearConsole: false,
  4204. logMaxCount: 999,
  4205. };
  4206. let logCount = 0;
  4207. /**
  4208. * 解析Error的堆栈获取实际调用者的函数名及函数所在的位置
  4209. * @param {string[]} stack
  4210. * @returns {{
  4211. * name: string,
  4212. * position: string,
  4213. * }}
  4214. */
  4215. let parseErrorStack = function (stack) {
  4216. let result = {
  4217. name: "",
  4218. position: "",
  4219. };
  4220. for (let stackString of stack) {
  4221. stackString = stackString.trim();
  4222. let stackFunctionName = stackString.match(/^at[\s]+(.+?)[\s]+/i);
  4223. let stackFunctionNamePosition = stackString.match(
  4224. /^at[\s]+.+[\s]+\((.+?)\)/i
  4225. );
  4226. if (stackFunctionName == null) {
  4227. continue;
  4228. }
  4229. stackFunctionName = stackFunctionName[stackFunctionName.length - 1];
  4230. stackFunctionNamePosition =
  4231. stackFunctionNamePosition[stackFunctionNamePosition.length - 1];
  4232. if (
  4233. stackFunctionName === "" ||
  4234. stackFunctionName.match(
  4235. new RegExp(
  4236. "(^Utils.Log.|.<anonymous>$|^Function.each|^NodeList.forEach|^k.fn.init.each)",
  4237. "g"
  4238. )
  4239. )
  4240. ) {
  4241. continue;
  4242. } else {
  4243. result.name = stackFunctionName;
  4244. result.position = stackFunctionNamePosition;
  4245. break;
  4246. }
  4247. }
  4248. if (result.position === "") {
  4249. let lastStackString = stack[stack.length - 1].trim();
  4250. if (lastStackString.startsWith("at chrome-extension://")) {
  4251. let lastStackMatch = lastStackString.match(/^at[\s]+(.+)/);
  4252. if (lastStackMatch) {
  4253. result.position = lastStackMatch[lastStackMatch.length - 1];
  4254. }
  4255. }
  4256. }
  4257. if (result.position === "") {
  4258. result.position = stack[stack.length - 1]
  4259. .trim()
  4260. .replace(/^at[\s]*/g, "");
  4261. }
  4262. return result;
  4263. };
  4264. /**
  4265. * 待恢复的函数或对象
  4266. */
  4267. let recoveryList = [];
  4268. /**
  4269. * 检测清理控制台
  4270. * @this {Utils.Log}
  4271. */
  4272. let checkClearConsole = function () {
  4273. logCount++;
  4274. if (details.autoClearConsole && logCount > details.logMaxCount) {
  4275. console.clear();
  4276. logCount = 0;
  4277. }
  4278. };
  4279. /**
  4280. * 输出内容
  4281. * @param {any} msg 需要输出的内容
  4282. * @param {string} color 颜色
  4283. * @param {string|undefined} otherStyle 其它CSS
  4284. * @this {Utils.Log}
  4285. */
  4286. let printContent = function (msg, color, otherStyle) {
  4287. checkClearConsole.apply(this);
  4288. otherStyle = otherStyle || "";
  4289. let stackSplit = new Error().stack.split("\n");
  4290. stackSplit.splice(0, 2);
  4291. let { name: callerName, position: callerPosition } =
  4292. parseErrorStack(stackSplit);
  4293. if (typeof msg === "object") {
  4294. /* 要输出的内容是个对象 */
  4295. if (details.tag) {
  4296. if (Array.isArray(msg) && msg.length < 5) {
  4297. console.log(
  4298. `%c[${this.tag}%c-%c${callerName}%c]%c `,
  4299. ...msgColorDetails,
  4300. `color: ${color};${otherStyle}`,
  4301. ...msg
  4302. );
  4303. } else {
  4304. console.log(
  4305. `%c[${this.tag}%c-%c${callerName}%c]%c `,
  4306. ...msgColorDetails,
  4307. `color: ${color};${otherStyle}`,
  4308. msg
  4309. );
  4310. }
  4311. } else {
  4312. if (Array.isArray(msg) && msg.length < 5) {
  4313. console.log(...msg);
  4314. } else {
  4315. console.log(msg);
  4316. }
  4317. }
  4318. } else {
  4319. if (details.tag) {
  4320. console.log(
  4321. `%c[${this.tag}%c-%c${callerName}%c]%c ${msg}`,
  4322. ...msgColorDetails,
  4323. `color: ${color};${otherStyle}`
  4324. );
  4325. } else {
  4326. console.log(`%c${msg}`, `color: ${color};${otherStyle}`);
  4327. }
  4328. }
  4329. if (details.debug) {
  4330. /* 如果开启调试模式,输出堆栈位置 */
  4331. console.log(callerPosition);
  4332. }
  4333. };
  4334. /**
  4335. * 前面的TAG标志
  4336. */
  4337. this.tag = _GM_info_?.script?.name || "Utils.Log";
  4338. /**
  4339. * 控制台-普通输出
  4340. * @param {any} msg 需要输出的内容,如果想输出多个,修改成数组,且数组内的长度最大值为4个
  4341. * @param {string|undefined} color 输出的颜色
  4342. * @param {string|undefined} otherStyle 其它CSS
  4343. */
  4344. this.info = function (msg, color = details.infoColor, otherStyle) {
  4345. printContent.call(this, msg, color, otherStyle);
  4346. };
  4347. /**
  4348. * 控制台-警告输出
  4349. * @param {any} msg 需要输出的内容,如果想输出多个,修改成数组,且数组内的长度最大值为4个
  4350. * @param {string|undefined} color 输出的颜色
  4351. * @param {string|undefined} otherStyle 其它CSS
  4352. */
  4353. this.warn = function (
  4354. msg,
  4355. color = details.warnColor,
  4356. otherStyle = "background: #FEF6D5;padding: 4px 6px 4px 0px;"
  4357. ) {
  4358. printContent.call(this, msg, color, otherStyle);
  4359. };
  4360. /**
  4361. * 控制台-错误输出
  4362. * @param {any} msg 需要输出的内容,如果想输出多个,修改成数组,且数组内的长度最大值为4个
  4363. * @param {string|undefined} color 输出的颜色
  4364. * @param {string|undefined} otherStyle 其它CSS
  4365. */
  4366. this.error = function (msg, color = details.errorColor, otherStyle) {
  4367. printContent.call(this, msg, color, otherStyle);
  4368. };
  4369. /**
  4370. * 控制台-成功输出
  4371. * @param {any} msg 需要输出的内容,如果想输出多个,修改成数组,且数组内的长度最大值为4个
  4372. * @param {string|undefined} color 输出的颜色
  4373. * @param {string|undefined} otherStyle 其它CSS
  4374. */
  4375. this.success = function (msg, color = details.successColor, otherStyle) {
  4376. printContent.call(this, msg, color, otherStyle);
  4377. };
  4378. /**
  4379. * 控制台-输出表格
  4380. * @param {object[]} msg
  4381. * @param {string|undefined} color 输出的颜色
  4382. * @param {string|undefined} otherStyle 其它CSS
  4383. * @example
  4384. * log.table([{"名字":"example","值":"123"},{"名字":"example2","值":"345"}])
  4385. */
  4386. this.table = function (msg, color = details.infoColor, otherStyle = "") {
  4387. checkClearConsole.apply(this);
  4388. let stack = new Error().stack.split("\n");
  4389. stack.splice(0, 1);
  4390. let errorStackParse = parseErrorStack(stack);
  4391. let stackFunctionName = errorStackParse.name;
  4392. let stackFunctionNamePosition = errorStackParse.position;
  4393. let callerName = stackFunctionName;
  4394. console.log(
  4395. `%c[${this.tag}%c-%c${callerName}%c]%c`,
  4396. ...msgColorDetails,
  4397. `color: ${color};${otherStyle}`
  4398. );
  4399. console.table(msg);
  4400. if (details.debug) {
  4401. console.log(stackFunctionNamePosition);
  4402. }
  4403. };
  4404. /**
  4405. * 配置Log对象的颜色
  4406. * @param {UtilsLogDetails} paramDetails 配置信息
  4407. */
  4408. this.config = function (paramDetails) {
  4409. details = Object.assign(details, paramDetails);
  4410. };
  4411. /**
  4412. * 禁用输出
  4413. */
  4414. this.disable = function () {
  4415. let that = this;
  4416. Object.keys(this)
  4417. .filter((keyName) => Boolean(keyName.match(/info|error|success|table/)))
  4418. .forEach((keyName) => {
  4419. let value = {};
  4420. value[keyName] = that[keyName];
  4421. recoveryList = [...recoveryList, value];
  4422. that[keyName] = () => {};
  4423. });
  4424. };
  4425. /**
  4426. * 恢复输出
  4427. */
  4428. this.recovery = function () {
  4429. let that = this;
  4430. recoveryList.forEach((item) => {
  4431. let keyName = Object.keys(item);
  4432. that[keyName] = item[keyName];
  4433. });
  4434. recoveryList = [];
  4435. };
  4436. };
  4437.  
  4438. /**
  4439. * 合并数组内的JSON的值字符串
  4440. * @param {[...any]} data 需要合并的数组
  4441. * @param {function|string|undefined} handleFunc 处理的函数|JSON的key
  4442. * @returns {string}
  4443. * @example
  4444. * Utils.mergeArrayToString([{"name":"数组内数据部分字段合并成字符串->"},{"name":"mergeToString"}],(item)=>{return item["name"]});
  4445. * > '数组内数据部分字段合并成字符串->mergeToString'
  4446. **/
  4447. Utils.mergeArrayToString = function (data, handleFunc) {
  4448. if (!(data instanceof Array)) {
  4449. throw new Error("Utils.mergeArrayToString 参数 data 必须为 Array 类型");
  4450. }
  4451. let content = "";
  4452. if (typeof handleFunc === "function") {
  4453. data.forEach((item) => {
  4454. content += handleFunc(item);
  4455. });
  4456. } else if (typeof handleFunc === "string") {
  4457. data.forEach((item) => {
  4458. content += item[handleFunc];
  4459. });
  4460. } else {
  4461. data.forEach((item) => {
  4462. Object.values(item)
  4463. .filter((item2) => typeof item2 === "string")
  4464. .forEach((item3) => {
  4465. content += item3;
  4466. });
  4467. });
  4468. }
  4469. return content;
  4470. };
  4471.  
  4472. /**
  4473. * 监听页面元素改变并处理
  4474. * @param {object|Node|NodeList} target 需要监听的元素,如果不存在,可以等待它出现
  4475. * @param {{
  4476. * config: MutationObserverInit,
  4477. * callback: MutationCallback
  4478. * }} observer_config MutationObserver的配置
  4479. * @example
  4480. Utils.mutationObserver(document.querySelector("div.xxxx"),{
  4481. "callback":(mutations, observer)=>{},
  4482. "config":{childList:true,attributes:true}
  4483. });
  4484. * @example
  4485. Utils.mutationObserver(document.querySelectorAll("div.xxxx"),{
  4486. "callback":(mutations, observer)=>{},
  4487. "config":{childList:true,attributes:true}}
  4488. );
  4489. * @example
  4490. Utils.mutationObserver($("div.xxxx"),{
  4491. "callback":(mutations, observer)=>{},
  4492. "config":{childList:true,attributes:true}}
  4493. );
  4494. **/
  4495. Utils.mutationObserver = function (target, observer_config) {
  4496. if (
  4497. !(target instanceof Node) &&
  4498. !(target instanceof NodeList) &&
  4499. !Utils.isJQuery(target)
  4500. ) {
  4501. throw new Error(
  4502. "Utils.mutationObserver 参数 target 必须为 Node|NodeList|jQuery类型"
  4503. );
  4504. }
  4505.  
  4506. let default_obverser_config = {
  4507. /* 监听到元素有反馈,需执行的函数 */
  4508. callback: () => {},
  4509. config: {
  4510. /**
  4511. * @type {boolean|undefined}
  4512. * + true 监听以 target 为根节点的整个子树。包括子树中所有节点的属性,而不仅仅是针对 target
  4513. * + false (默认) 不生效
  4514. */
  4515. subtree: void 0,
  4516. /**
  4517. * @type {boolean|undefined}
  4518. * + true 监听 target 节点中发生的节点的新增与删除(同时,如果 subtree 为 true,会针对整个子树生效)
  4519. * + false (默认) 不生效
  4520. */
  4521. childList: void 0,
  4522. /**
  4523. * @type {boolean|undefined}
  4524. * + true 观察所有监听的节点属性值的变化。默认值为 true,当声明了 attributeFilter 或 attributeOldValue
  4525. * + false (默认) 不生效
  4526. */
  4527. attributes: void 0,
  4528. /**
  4529. * 一个用于声明哪些属性名会被监听的数组。如果不声明该属性,所有属性的变化都将触发通知
  4530. * @type {[...string]|undefined}
  4531. */
  4532. attributeFilter: void 0,
  4533. /**
  4534. * @type {boolean|undefined}
  4535. * + true 记录上一次被监听的节点的属性变化;可查阅 MutationObserver 中的 Monitoring attribute values 了解关于观察属性变化和属性值记录的详情
  4536. * + false (默认) 不生效
  4537. */
  4538. attributeOldValue: void 0,
  4539. /**
  4540. * @type {boolean|undefined}
  4541. * + true 监听声明的 target 节点上所有字符的变化。默认值为 true,如果声明了 characterDataOldValue
  4542. * + false (默认) 不生效
  4543. */
  4544. characterData: void 0,
  4545. /**
  4546. * @type {boolean|undefined}
  4547. * + true 记录前一个被监听的节点中发生的文本变化
  4548. * + false (默认) 不生效
  4549. */
  4550. characterDataOldValue: void 0,
  4551. },
  4552. };
  4553. observer_config = Utils.assign(default_obverser_config, observer_config);
  4554. let MutationObserver =
  4555. window.MutationObserver ||
  4556. window.webkitMutationObserver ||
  4557. window.MozMutationObserver;
  4558. let mutationObserver = new MutationObserver(function (mutations, observer) {
  4559. observer_config?.callback(mutations, observer);
  4560. });
  4561. if (target instanceof Node) {
  4562. /* 传入的参数是节点元素 */
  4563. mutationObserver.observe(target, observer_config.config);
  4564. } else if (target instanceof NodeList) {
  4565. /* 传入的参数是节点元素数组 */
  4566. target.forEach((item) => {
  4567. mutationObserver.observe(item, observer_config.config);
  4568. });
  4569. } else if (Utils.isJQuery(target)) {
  4570. /* 传入的参数是jQuery对象 */
  4571. target.each((index, item) => {
  4572. mutationObserver.observe(item, observer_config.config);
  4573. });
  4574. } else {
  4575. /* 未知 */
  4576. console.error("Utils.mutationObserver 未知参数", arguments);
  4577. }
  4578. return mutationObserver;
  4579. };
  4580.  
  4581. /**
  4582. * 去除全局window下的Utils,返回控制权
  4583. * @returns {Utils}
  4584. * @example
  4585. * let utils = Utils.noConflict();
  4586. * > ...
  4587. */
  4588. Utils.noConflict = function () {
  4589. if (window.Utils) {
  4590. delete window.Utils;
  4591. }
  4592. if (AnotherUtils) {
  4593. window.Utils = AnotherUtils;
  4594. }
  4595. return Utils;
  4596. };
  4597.  
  4598. /**
  4599. * 恢复/释放该对象内的为function,让它无效/有效
  4600. * @param {object} needReleaseObject 需要操作的对象
  4601. * @param {string} needReleaseName 需要操作的对象的名字
  4602. * @param {array} [functionNameList=[]] 需要释放的方法,如果为空,默认全部方法
  4603. * @param {boolean} [release=true]
  4604. * + true (默认) 释放该对象下的某些方法
  4605. * + false 恢复该对象下的某些方法
  4606. * @example
  4607. // 释放该方法
  4608. Utils.noConflictFunc(console,"console",["log"],true);
  4609. console.log;
  4610. > () => {}
  4611.  
  4612. * @example
  4613. // 恢复该方法
  4614. Utils.noConflictFunc(console,"console",["log"],false);
  4615. console.log;
  4616. > ƒ log() { [native code] }
  4617.  
  4618. * @example
  4619. // 释放所有方法
  4620. Utils.noConflictFunc(console,"console",[],true);
  4621. console.debug;
  4622. > () => {}
  4623.  
  4624. * @example
  4625. // 恢复所有方法
  4626. Utils.noConflictFunc(console,"console",[],false);
  4627. console.debug;
  4628. > ƒ log() { [native code] }
  4629. **/
  4630. Utils.noConflictFunc = function (
  4631. needReleaseObject,
  4632. needReleaseName,
  4633. functionNameList = [],
  4634. release = true
  4635. ) {
  4636. if (typeof needReleaseObject !== "object") {
  4637. throw new Error(
  4638. "Utils.noConflictFunc 参数 needReleaseObject 必须为 object 类型"
  4639. );
  4640. }
  4641. if (typeof needReleaseName !== "string") {
  4642. throw new Error(
  4643. "Utils.noConflictFunc 参数 needReleaseName 必须为 string 类型"
  4644. );
  4645. }
  4646. if (!Array.isArray(functionNameList)) {
  4647. throw new Error(
  4648. "Utils.noConflictFunc 参数 functionNameList 必须为 Array 类型"
  4649. );
  4650. }
  4651. let needReleaseKey = "__" + needReleaseName;
  4652. /**
  4653. * 复制对象
  4654. * @param {object} obj
  4655. * @returns {object}
  4656. */
  4657. function cloneObj(obj) {
  4658. let newObj = {};
  4659. if (obj instanceof Array) {
  4660. newObj = [];
  4661. }
  4662. for (let key in obj) {
  4663. let val = obj[key];
  4664. newObj[key] = typeof val === "object" ? cloneObj(val) : val;
  4665. }
  4666. return newObj;
  4667. }
  4668. /**
  4669. * 释放所有
  4670. */
  4671. function releaseAll() {
  4672. if (typeof window[needReleaseKey] !== "undefined") {
  4673. /* 已存在 */
  4674. return;
  4675. }
  4676. window[needReleaseKey] = cloneObj(needReleaseObject);
  4677. Object.values(needReleaseObject).forEach((value) => {
  4678. if (typeof value === "function") {
  4679. needReleaseObject[value.name] = () => {};
  4680. }
  4681. });
  4682. }
  4683. /**
  4684. * 释放单个
  4685. */
  4686. function releaseOne() {
  4687. Array.from(functionNameList).forEach((item) => {
  4688. Object.values(needReleaseObject).forEach((value) => {
  4689. if (typeof value === "function") {
  4690. if (typeof window[needReleaseKey] === "undefined") {
  4691. window[needReleaseKey] = {};
  4692. }
  4693. if (item === value.name) {
  4694. window[needReleaseKey][value.name] =
  4695. needReleaseObject[value.name];
  4696. needReleaseObject[value.name] = () => {};
  4697. }
  4698. }
  4699. });
  4700. });
  4701. }
  4702. /**
  4703. * 恢复所有
  4704. */
  4705. function recoveryAll() {
  4706. if (typeof window[needReleaseKey] === "undefined") {
  4707. /* 未存在 */
  4708. return;
  4709. }
  4710. Object.assign(needReleaseObject, window[needReleaseKey]);
  4711. delete window[needReleaseKey];
  4712. }
  4713.  
  4714. /**
  4715. * 恢复单个
  4716. */
  4717. function recoveryOne() {
  4718. if (typeof window[needReleaseKey] === "undefined") {
  4719. /* 未存在 */
  4720. return;
  4721. }
  4722. Array.from(functionNameList).forEach((item) => {
  4723. if (window[needReleaseKey][item]) {
  4724. needReleaseObject[item] = window[needReleaseKey][item];
  4725. delete window[needReleaseKey][item];
  4726. if (Object.keys(window[needReleaseKey]).length === 0) {
  4727. delete window[needReleaseKey];
  4728. }
  4729. }
  4730. });
  4731. }
  4732. if (release) {
  4733. /* 释放 */
  4734. if (functionNameList.length === 0) {
  4735. releaseAll();
  4736. } else {
  4737. /* 对单个进行操作 */
  4738. releaseOne();
  4739. }
  4740. } else {
  4741. /* 恢复 */
  4742. if (functionNameList.length === 0) {
  4743. recoveryAll();
  4744. } else {
  4745. /* 对单个进行操作 */
  4746. recoveryOne();
  4747. }
  4748. }
  4749. };
  4750. /**
  4751. * base64转blob
  4752. * @param {string} dataUri base64的数据
  4753. * @returns {string} blob的链接
  4754. * @example
  4755. * Utils.parseBase64ToBlob("data:image/jpeg;base64,.....");
  4756. * > blob://xxxxxxx
  4757. **/
  4758. Utils.parseBase64ToBlob = function (dataUri) {
  4759. if (typeof dataUri !== "string") {
  4760. throw new Error(
  4761. "Utils.parseBase64ToBlob 参数 dataUri 必须为 string 类型"
  4762. );
  4763. }
  4764. let dataUriSplit = dataUri.split(","),
  4765. dataUriMime = dataUriSplit[0].match(/:(.*?);/)[1],
  4766. dataUriBase64Str = atob(dataUriSplit[1]),
  4767. dataUriLength = dataUriBase64Str.length,
  4768. u8arr = new Uint8Array(dataUriLength);
  4769. while (dataUriLength--) {
  4770. u8arr[dataUriLength] = dataUriBase64Str.charCodeAt(dataUriLength);
  4771. }
  4772. return new Blob([u8arr], {
  4773. type: dataUriMime,
  4774. });
  4775. };
  4776.  
  4777. /**
  4778. * base64转File对象
  4779. * @param {string} dataUri base64的数据
  4780. * @param {string} [fileName="example"] 文件名,默认为example
  4781. * @returns {File} blob的链接
  4782. * @example
  4783. * Utils.parseBase64ToFile("data:image/jpeg;base64,.....","测试文件");
  4784. * > object
  4785. **/
  4786. Utils.parseBase64ToFile = function (dataUri, fileName = "example") {
  4787. if (typeof dataUri !== "string") {
  4788. throw new Error(
  4789. "Utils.parseBase64ToFile 参数 dataUri 必须为 string 类型"
  4790. );
  4791. }
  4792. if (typeof fileName !== "string") {
  4793. throw new Error(
  4794. "Utils.parseBase64ToFile 参数 fileName 必须为 string 类型"
  4795. );
  4796. }
  4797. let dataUriSplit = dataUri.split(","),
  4798. dataUriMime = dataUriSplit[0].match(/:(.*?);/)[1],
  4799. dataUriBase64Str = atob(dataUriSplit[1]),
  4800. dataUriLength = dataUriBase64Str.length,
  4801. u8arr = new Uint8Array(dataUriLength);
  4802. while (dataUriLength--) {
  4803. u8arr[dataUriLength] = dataUriBase64Str.charCodeAt(dataUriLength);
  4804. }
  4805. return new File([u8arr], fileName, {
  4806. type: dataUriMime,
  4807. });
  4808. };
  4809.  
  4810. /**
  4811. * 将正则匹配到的结果取出最后一个值并转换成int格式
  4812. * @param {[...any]} [matchList=[]] 正则匹配的列表
  4813. * @param {number|string} [defaultValue=0] 正则匹配的列表为空时,或者正则匹配的列表最后一项不为Int,返回该默认值0
  4814. * @example
  4815. * Utils.parseInt(["dadaadada123124","123124"],0);
  4816. * > 123124
  4817. *
  4818. * @example
  4819. * Utils.parseInt(null,0);
  4820. * > 0
  4821. * @example
  4822. * Utils.parseInt(["aaaaaa"]);
  4823. * > 0
  4824. *
  4825. * @example
  4826. * Utils.parseInt(["aaaaaa"],"66");
  4827. * > 66
  4828. *
  4829. * @example
  4830. * Utils.parseInt(["aaaaaaa"],"aa");
  4831. * > NaN
  4832. **/
  4833. Utils.parseInt = function (matchList = [], defaultValue = 0) {
  4834. if (matchList == null) {
  4835. return parseInt(defaultValue);
  4836. }
  4837. let parseValue = parseInt(matchList[matchList.length - 1]);
  4838. if (isNaN(parseValue)) {
  4839. parseValue = parseInt(defaultValue);
  4840. }
  4841. return parseValue;
  4842. };
  4843.  
  4844. /**
  4845. * blob转File对象
  4846. * @param {string} blobUrl 需要转换的blob的链接
  4847. * @param {string} [fileName="example"] 转换成的File对象的文件名称,默认为example
  4848. * @returns {Promise<File|Error>}
  4849. * @example
  4850. * Utils.parseBlobToFile("blob://xxxxx");
  4851. * > object
  4852. **/
  4853. Utils.parseBlobToFile = async function (blobUrl, fileName = "example") {
  4854. return new Promise((resolve, reject) => {
  4855. fetch(blobUrl)
  4856. .then((response) => response.blob())
  4857. .then((blob) => {
  4858. const file = new File([blob], fileName, { type: blob.type });
  4859. resolve(file);
  4860. })
  4861. .catch((error) => {
  4862. console.error("Error:", error);
  4863. reject(error);
  4864. });
  4865. });
  4866. };
  4867.  
  4868. /**
  4869. * 解析CDATA格式的内容字符串
  4870. * @param {string} [text=""] 传入CDATA字符串
  4871. * @returns {string} 返回解析出的内容
  4872. * @example
  4873. * let xml = "<root><![CDATA[This is some CDATA content.]]></root>";
  4874. * console.log(Utils.parseCDATA(xml));
  4875. * > This is some CDATA content.
  4876. */
  4877. Utils.parseCDATA = function (text = "") {
  4878. let result = "";
  4879. let cdataRegexp = /<\!\[CDATA\[([\s\S]*)\]\]>/;
  4880. let cdataMatch = cdataRegexp.exec(text.trim());
  4881. if (cdataMatch && cdataMatch.length > 1) {
  4882. result = cdataMatch[cdataMatch.length - 1];
  4883. }
  4884. return result;
  4885. };
  4886.  
  4887. /**
  4888. * 【异步函数】File对象转base64
  4889. * @async
  4890. * @param {File} fileObj 需要转换的File对象
  4891. * @returns {Promise<string>}
  4892. * @example
  4893. * await Utils.parseFileToBase64(object);
  4894. * > 'data:image/jpeg:base64/,xxxxxx'
  4895. **/
  4896. Utils.parseFileToBase64 = async function (fileObj) {
  4897. let reader = new FileReader();
  4898. reader.readAsDataURL(fileObj);
  4899. return new Promise((resolve) => {
  4900. reader.onload = function (event) {
  4901. resolve(event.target.result);
  4902. };
  4903. });
  4904. };
  4905.  
  4906. /**
  4907. * 解析字符串
  4908. * @param {string} text 要解析的 DOMString。它必须包含 HTML、xml、xhtml+xml 或 svg 文档。
  4909. * @param {"text/html"|"text/xml"|"application/xml"|"application/xhtml+xml"|"image/svg+xml"} [mimeType="text/html"] 解析成的类型,包括:text/html(默认)、text/xml、application/xml、application/xhtml+xml、image/svg+xml
  4910. * @returns {HTMLElement|XMLDocument|SVGElement}
  4911. * @example
  4912. * Utils.parseFromString("<p>123<p>");
  4913. * > #document
  4914. */
  4915. Utils.parseFromString = function (text, mimeType = "text/html") {
  4916. let parser = new DOMParser();
  4917. return parser.parseFromString(text, mimeType);
  4918. };
  4919.  
  4920. /**
  4921. * 阻止事件传递
  4922. * @param {HTMLElement} element 要进行处理的元素
  4923. * @param {?string|[...string]} eventNameList 要阻止的事件名|列表
  4924. * @param {Event|undefined} paramEvent 事件
  4925. * @example
  4926. * Utils.preventEvent(document.querySelector("a"),"click")
  4927. * @example
  4928. * Utils.preventEvent(event);
  4929. */
  4930. Utils.preventEvent = function (element, eventNameList = []) {
  4931. function stopEvent(event) {
  4932. /* 阻止事件的默认行为发生。例如,当点击一个链接时,浏览器会默认打开链接的URL */
  4933. event?.preventDefault();
  4934. /* 停止事件的传播,阻止它继续向更上层的元素冒泡,事件将不会再传播给其他的元素 */
  4935. event?.stopPropagation();
  4936. /* 阻止事件传播,并且还能阻止元素上的其他事件处理程序被触发 */
  4937. event?.stopImmediatePropagation();
  4938. return false;
  4939. }
  4940. if (arguments.length === 1) {
  4941. return stopEvent(arguments[0]);
  4942. } else if (arguments.length === 2) {
  4943. if (typeof eventNameList === "string") {
  4944. eventNameList = [eventNameList];
  4945. }
  4946. eventNameList.forEach((eventName) => {
  4947. element.addEventListener(eventName, function (event) {
  4948. return stopEvent(event);
  4949. });
  4950. });
  4951. }
  4952. };
  4953.  
  4954. /**
  4955. * 在canvas元素节点上绘制进度圆圈
  4956. * @param {{
  4957. * canvasNode : ?HTMLCanvasElement,
  4958. * deg: number,
  4959. * progress: number,
  4960. * lineWidth: number,
  4961. * lineBgColor: string,
  4962. * lineColor: string,
  4963. * fontSize: string,
  4964. * circleRadius: string,
  4965. * draw: ?()=>{}
  4966. * }} paramConfig 配置信息
  4967. * @example
  4968. let progress = new Utils.Process({canvasNode:document.querySelector("canvas")});
  4969. progress.draw();
  4970. * **/
  4971. Utils.Progress = function (paramConfig) {
  4972. this.config = {
  4973. /**
  4974. * canvas元素节点
  4975. * @type {HTMLCanvasElement}
  4976. */
  4977. canvasNode: null,
  4978. /**
  4979. * 绘制角度
  4980. */
  4981. deg: 95,
  4982. /**
  4983. * 进度
  4984. */
  4985. progress: 0,
  4986. /**
  4987. * 绘制的线宽度
  4988. */
  4989. lineWidth: 10,
  4990. /**
  4991. * 绘制的背景颜色
  4992. */
  4993. lineBgColor: "#1e637c",
  4994. /**
  4995. * 绘制的线的颜色
  4996. */
  4997. lineColor: "#25deff",
  4998. /**
  4999. * 绘制的字体颜色
  5000. */
  5001. textColor: "#000000",
  5002. /**
  5003. * 绘制的字体大小(px)
  5004. */
  5005. fontSize: 22,
  5006. /**
  5007. * 绘制的圆的半径
  5008. */
  5009. circleRadius: 50,
  5010. /**
  5011. * 控制绘制的函数
  5012. */
  5013. draw: () => {},
  5014. };
  5015. this.config = Utils.assign(this.config, paramConfig);
  5016. if (!(this.config.canvasNode instanceof HTMLCanvasElement)) {
  5017. throw new Error(
  5018. "Utils.Progress 参数 canvasNode 必须是 HTMLCanvasElement"
  5019. );
  5020. }
  5021. /* 获取画笔 */
  5022. let ctx = this.config.canvasNode.getContext("2d");
  5023. /* 元素宽度 */
  5024. let width = this.config.canvasNode.width;
  5025. /* 元素高度 */
  5026. let height = this.config.canvasNode.height;
  5027.  
  5028. /* 清除锯齿 */
  5029. if (window.devicePixelRatio) {
  5030. this.config.canvasNode.style.width = width + "px";
  5031. this.config.canvasNode.style.height = height + "px";
  5032. this.config.canvasNode.height = height * window.devicePixelRatio;
  5033. this.config.canvasNode.width = width * window.devicePixelRatio;
  5034. ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
  5035. }
  5036. /* 设置线宽 */
  5037. ctx.lineWidth = this.config.lineWidth;
  5038. /* 绘制 */
  5039. this.draw = function () {
  5040. let degActive = (this.config.progress * 360) / 100;
  5041. /* 清除画布 */
  5042. ctx.clearRect(0, 0, width, height);
  5043. /* 开始绘制底圆 */
  5044. ctx.beginPath();
  5045. ctx.arc(width / 2, height / 2, this.config.circleRadius, 1, 8);
  5046. ctx.strokeStyle = this.config.lineBgColor;
  5047. ctx.stroke();
  5048. /* 开始绘制动态圆 */
  5049. ctx.beginPath();
  5050. ctx.arc(
  5051. width / 2,
  5052. height / 2,
  5053. this.config.circleRadius,
  5054. -Math.PI / 2,
  5055. (degActive * Math.PI) / 180 - Math.PI / 2
  5056. );
  5057. ctx.strokeStyle = this.config.lineColor;
  5058. ctx.stroke();
  5059. /* 获取百分比 */
  5060. let txt = parseInt(this.config.progress) + "%";
  5061. ctx.font = this.config.fontSize + "px SimHei";
  5062. /* 获取文本宽度 */
  5063. let w = ctx.measureText(txt).width;
  5064. let h = this.config.fontSize / 2;
  5065. ctx.fillStyle = this.config.textColor;
  5066. ctx.fillText(txt, width / 2 - w / 2, height / 2 + h / 2);
  5067. }.bind(this);
  5068. };
  5069.  
  5070. /**
  5071. * 劫持Event的isTrust为true,注入时刻,ducument-start
  5072. * @example
  5073. * Utils.registerTrustClickEvent()
  5074. */
  5075. Utils.registerTrustClickEvent = function (isTrustValue = true) {
  5076. function trustEvent(event) {
  5077. return new Proxy(event, {
  5078. get: function (target, property) {
  5079. if (property === "isTrusted") {
  5080. return isTrustValue;
  5081. } else {
  5082. return Reflect.get(target, property);
  5083. }
  5084. },
  5085. });
  5086. }
  5087. const originalListener = EventTarget.prototype.addEventListener;
  5088. EventTarget.prototype.addEventListener = function () {
  5089. if (arguments[0] === "click") {
  5090. const fn = arguments[1];
  5091. arguments[1] = function (e) {
  5092. fn.call(this, trustEvent(e));
  5093. };
  5094. }
  5095. return originalListener.apply(this, arguments);
  5096. };
  5097. };
  5098.  
  5099. /**
  5100. * 复制到剪贴板
  5101. * @param {any} data - 需要复制到剪贴板的文本
  5102. * @param {{type:string,mimetype:string}|string} [info="text/plain"]
  5103. * @example
  5104. * Utils.setClip({1:2});
  5105. * > {"1":2}
  5106. * @example
  5107. * Utils.setClip( ()=>{
  5108. * console.log(1)
  5109. * });
  5110. * > ()=>{console.log(1)}
  5111. * @example
  5112. * Utils.setClip("xxxx");
  5113. * > xxxx
  5114. * @example
  5115. * Utils.setClip("xxxx","html");
  5116. * > xxxx
  5117. * @example
  5118. * Utils.setClip("xxxx","text/plain");
  5119. * > xxxx
  5120. **/
  5121. Utils.setClip = async function (data, info = "text/plain") {
  5122. if (typeof data === "object") {
  5123. if (data instanceof Element) {
  5124. data = data.outerHTML;
  5125. } else {
  5126. data = JSON.stringify(data);
  5127. }
  5128. } else if (typeof data !== "string") {
  5129. data = data.toString();
  5130. }
  5131. let textType = typeof info === "object" ? info.type : info;
  5132. if (textType.includes("html")) {
  5133. textType = "text/html";
  5134. } else {
  5135. textType = "text/plain";
  5136. }
  5137. let textBlob = new Blob([data], { type: textType });
  5138. let clipboardObject = navigator.clipboard;
  5139. /**
  5140. * 第二种方式进行复制
  5141. * @param {(value: any) => void} _resolve_ 回调
  5142. * @param {string} _copyText_ 复制的文字
  5143. */
  5144. function anotherCopy(_resolve_, _copyText_) {
  5145. let copyElement = document.createElement("textarea");
  5146. copyElement.value = _copyText_;
  5147. copyElement.setAttribute("type", "text");
  5148. copyElement.setAttribute("style", "opacity:0;position:absolute;");
  5149. copyElement.setAttribute("readonly", "readonly");
  5150. document.body.appendChild(copyElement);
  5151. copyElement.select();
  5152. document.execCommand("copy");
  5153. document.body.removeChild(copyElement);
  5154. _resolve_();
  5155. }
  5156. /**
  5157. * 运行复制
  5158. * @param {(value: any) => void} _resolve_ 回调
  5159. */
  5160. function runCopy(_resolve_) {
  5161. if (typeof ClipboardItem === "undefined") {
  5162. console.error("当前环境中不存在ClipboardItem对象,使用第二种方式");
  5163. anotherCopy(_resolve_, data);
  5164. } else if (clipboardObject) {
  5165. clipboardObject
  5166. .write([
  5167. new ClipboardItem({
  5168. [textType]: textBlob,
  5169. }),
  5170. ])
  5171. .then(() => {
  5172. _resolve_();
  5173. })
  5174. .catch((err) => {
  5175. console.error("复制失败,使用第二种方式,error👉", err);
  5176. anotherCopy(_resolve_, data);
  5177. });
  5178. } else {
  5179. anotherCopy(_resolve_, data);
  5180. }
  5181. }
  5182. return new Promise((resolve) => {
  5183. if (document.hasFocus()) {
  5184. runCopy(resolve);
  5185. } else {
  5186. window.addEventListener(
  5187. "focus",
  5188. () => {
  5189. runCopy(resolve);
  5190. },
  5191. { once: true }
  5192. );
  5193. }
  5194. });
  5195. };
  5196.  
  5197. /**
  5198. * 【异步函数】等待N秒执行函数
  5199. * @param {function|string} delayToExecuteFunction 待执行的函数(字符串)
  5200. * @param {number} [delayTime=0] 延时时间(ms)
  5201. * @example
  5202. * await Utils.setTimeout(()=>{}, 2500);
  5203. * > ƒ tryCatchObj() {}
  5204. * @example
  5205. * await Utils.setTimeout("()=>{console.log(12345)}", 2500);
  5206. * > ƒ tryCatchObj() {}
  5207. **/
  5208. Utils.setTimeout = async function (delayToExecuteFunction, delayTime = 0) {
  5209. if (
  5210. typeof delayToExecuteFunction !== "function" &&
  5211. typeof delayToExecuteFunction !== "string"
  5212. ) {
  5213. throw new Error("Utils.setTimeout 参数 func 必须为 function|string 类型");
  5214. }
  5215. if (typeof delayTime !== "number") {
  5216. throw new Error("Utils.setTimeout 参数 delayTime 必须为 number 类型");
  5217. }
  5218. return new Promise((resolve) => {
  5219. setTimeout(() => {
  5220. resolve(Utils.tryCatch().run(delayToExecuteFunction));
  5221. }, delayTime);
  5222. });
  5223. };
  5224.  
  5225. /**
  5226. * 【异步函数】延迟xxx毫秒
  5227. * @param {number} [delayTime=0] 延时时间(ms)
  5228. * @example
  5229. * await Utils.sleep(2500)
  5230. **/
  5231. Utils.sleep = async function (delayTime = 0) {
  5232. if (typeof delayTime !== "number") {
  5233. throw new Error("Utils.sleep 参数 delayTime 必须为 number 类型");
  5234. }
  5235. return new Promise((resolve) => {
  5236. setTimeout(() => {
  5237. resolve();
  5238. }, delayTime);
  5239. });
  5240. };
  5241.  
  5242. /**
  5243. * 向右拖动滑块
  5244. * @param {string|Element|Node} selector 选择器|元素
  5245. * @param {number} [offsetX= window.innerWidth] 水平拖动长度,默认浏览器宽度
  5246. * @example
  5247. * Utils.dragSlider("#xxxx");
  5248. * @example
  5249. * Utils.dragSlider("#xxxx",100);
  5250. */
  5251. Utils.dragSlider = function (selector, offsetX = window.innerWidth) {
  5252. function initMouseEvent(eventName, offSetX, offSetY) {
  5253. let win = unsafeWindow || window;
  5254. let mouseEvent = document.createEvent("MouseEvents");
  5255. mouseEvent.initMouseEvent(
  5256. eventName,
  5257. true,
  5258. true,
  5259. win,
  5260. 0,
  5261. offSetX,
  5262. offSetY,
  5263. offSetX,
  5264. offSetY,
  5265. false,
  5266. false,
  5267. false,
  5268. false,
  5269. 0,
  5270. null
  5271. );
  5272. return mouseEvent;
  5273. }
  5274. let sliderElement =
  5275. typeof selector === "string"
  5276. ? document.querySelector(selector)
  5277. : selector;
  5278. if (
  5279. !(sliderElement instanceof Node) ||
  5280. !(sliderElement instanceof Element)
  5281. ) {
  5282. throw new Error("Utils.dragSlider 参数selector 必须为Node/Element类型");
  5283. }
  5284. let rect = sliderElement.getBoundingClientRect(),
  5285. x0 = rect.x || rect.left,
  5286. y0 = rect.y || rect.top,
  5287. x1 = x0 + offsetX,
  5288. y1 = y0;
  5289. sliderElement.dispatchEvent(initMouseEvent("mousedown", x0, y0));
  5290. sliderElement.dispatchEvent(initMouseEvent("mousemove", x1, y1));
  5291. sliderElement.dispatchEvent(initMouseEvent("mouseleave", x1, y1));
  5292. sliderElement.dispatchEvent(initMouseEvent("mouseout", x1, y1));
  5293. };
  5294.  
  5295. /**
  5296. * 使浏览器进入全屏
  5297. * @param {?HTMLElement} element 目标元素
  5298. * @param {?FullscreenOptions} options 配置,一般不用
  5299. * @example
  5300. * Utils.enterFullscreen();
  5301. */
  5302. Utils.enterFullscreen = function (element = document.body, options) {
  5303. try {
  5304. if (element.requestFullscreen) {
  5305. element.requestFullscreen(options);
  5306. } else if (element.webkitRequestFullScreen) {
  5307. element.webkitRequestFullScreen();
  5308. } else if (element.mozRequestFullScreen) {
  5309. element.mozRequestFullScreen();
  5310. } else if (element.msRequestFullscreen) {
  5311. element.msRequestFullscreen();
  5312. } else {
  5313. throw new Error("该浏览器不支持全屏API");
  5314. }
  5315. } catch (err) {
  5316. console.error(err);
  5317. }
  5318. };
  5319.  
  5320. /**
  5321. * 数组按照内部某个值的大小比对排序,如[{"time":"2022-1-1"},{"time":"2022-2-2"}]
  5322. * @param {any[]|NodeList|function} data 数据|获取数据的方法
  5323. * @param {string|function} getPropertyValueFunc 数组内部项的某个属性的值的方法,参数为这个项
  5324. * @param {boolean} [sortByDesc=true] 排序方式,默认true倒序(值最大排第一个,如:6、5、4、3...),false为升序(值最小排第一个,如:1、2、3、4...)
  5325. * @returns {any[]} 返回比较排序完成的数组
  5326. * @example
  5327. * Utils.sortListByProperty([{"time":"2022-1-1"},{"time":"2022-2-2"}],(item)=>{return item["time"]})
  5328. * > [{time: '2022-2-2'},{time: '2022-1-1'}]
  5329. * @example
  5330. * Utils.sortListByProperty([{"time":"2022-1-1"},{"time":"2022-2-2"}],(item)=>{return item["time"]},false)
  5331. * > [{time: '2022-1-1'},{time: '2022-2-2'}]
  5332. **/
  5333. Utils.sortListByProperty = function (
  5334. data,
  5335. getPropertyValueFunc,
  5336. sortByDesc = true
  5337. ) {
  5338. if (
  5339. typeof getPropertyValueFunc !== "function" &&
  5340. typeof getPropertyValueFunc !== "string"
  5341. ) {
  5342. throw new Error(
  5343. "Utils.sortListByProperty 参数 getPropertyValueFunc 必须为 function|string 类型"
  5344. );
  5345. }
  5346. if (typeof sortByDesc !== "boolean") {
  5347. throw new Error(
  5348. "Utils.sortListByProperty 参数 sortByDesc 必须为 boolean 类型"
  5349. );
  5350. }
  5351. let getObjValue = function (obj) {
  5352. return typeof getPropertyValueFunc === "string"
  5353. ? obj[getPropertyValueFunc]
  5354. : getPropertyValueFunc(obj);
  5355. };
  5356. /**
  5357. * 排序方法
  5358. * @param {any} after_obj
  5359. * @param {any} before_obj
  5360. * @returns
  5361. */
  5362. let sortFunc = function (after_obj, before_obj) {
  5363. let beforeValue = getObjValue(before_obj); /* 前 */
  5364. let afterValue = getObjValue(after_obj); /* 后 */
  5365. if (sortByDesc) {
  5366. if (afterValue > beforeValue) {
  5367. return -1;
  5368. } else if (afterValue < beforeValue) {
  5369. return 1;
  5370. } else {
  5371. return 0;
  5372. }
  5373. } else {
  5374. if (afterValue < beforeValue) {
  5375. return -1;
  5376. } else if (afterValue > beforeValue) {
  5377. return 1;
  5378. } else {
  5379. return 0;
  5380. }
  5381. }
  5382. };
  5383. /**
  5384. * 排序元素方法
  5385. * @param {NodeList|jQuery} nodeList 元素列表
  5386. * @param {function} getNodeListFunc 获取元素列表的函数
  5387. */
  5388. let sortNodeFunc = function (nodeList, getNodeListFunc) {
  5389. let nodeListLength = nodeList.length;
  5390. for (let i = 0; i < nodeListLength - 1; i++) {
  5391. for (let j = 0; j < nodeListLength - 1 - i; j++) {
  5392. let beforeNode = nodeList[j];
  5393. let afterNode = nodeList[j + 1];
  5394. let beforeValue = getObjValue(beforeNode); /* 前 */
  5395. let afterValue = getObjValue(afterNode); /* 后 */
  5396. if (
  5397. (sortByDesc == true && beforeValue < afterValue) ||
  5398. (sortByDesc == false && beforeValue > afterValue)
  5399. ) {
  5400. /* 升序/降序 */
  5401. /* 相邻元素两两对比 */
  5402. let temp = beforeNode.nextElementSibling;
  5403. afterNode.after(beforeNode);
  5404. if (temp == null) {
  5405. /* 如果为空,那么是最后一个元素,使用append */
  5406. temp.parentNode.appendChild(afterNode);
  5407. } else {
  5408. /* 不为空,使用before */
  5409. temp.before(afterNode);
  5410. }
  5411. nodeList = getNodeListFunc();
  5412. }
  5413. }
  5414. }
  5415. };
  5416. let result = data;
  5417. let getDataFunc = null;
  5418. if (data instanceof Function) {
  5419. getDataFunc = data;
  5420. data = data();
  5421. }
  5422. if (Array.isArray(data)) {
  5423. data.sort(sortFunc);
  5424. } else if (data instanceof NodeList || Utils.isJQuery(data)) {
  5425. sortNodeFunc(data, getDataFunc);
  5426. result = getDataFunc();
  5427. } else {
  5428. throw new Error(
  5429. "Utils.sortListByProperty 参数 data 必须为 Array|NodeList|jQuery 类型"
  5430. );
  5431. }
  5432. return result;
  5433. };
  5434.  
  5435. /**
  5436. * 字符串转正则,用于把字符串中不规范的字符进行转义
  5437. * @param {string} targetString 需要进行转换的字符串
  5438. * @param {"g"|"i"|"m"|"u"|"y"} [flags="ig"] 正则标志
  5439. * @returns {RegExp}
  5440. */
  5441. Utils.stringToRegular = function (targetString, flags = "ig") {
  5442. let reg;
  5443. flags = flags.toLowerCase();
  5444. if (typeof targetString === "string") {
  5445. reg = new RegExp(
  5446. targetString.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"),
  5447. flags
  5448. );
  5449. } else if (targetString instanceof RegExp) {
  5450. reg = targetString;
  5451. } else {
  5452. throw new Error(
  5453. "Utils.stringToRegular 参数targetString必须是string|Regexp类型"
  5454. );
  5455. }
  5456. return reg;
  5457. };
  5458.  
  5459. /**
  5460. * 字符串首字母转大写
  5461. * @param {string} targetString 目标字符串
  5462. * @param {boolean} [otherStrToLowerCase=false] 剩余部分字符串转小写,默认false
  5463. * @returns
  5464. */
  5465. Utils.stringTitleToUpperCase = function (
  5466. targetString,
  5467. otherStrToLowerCase = false
  5468. ) {
  5469. let newTargetString = targetString.slice(0, 1).toUpperCase();
  5470. if (otherStrToLowerCase) {
  5471. newTargetString = newTargetString + targetString.slice(1).toLowerCase();
  5472. } else {
  5473. newTargetString = newTargetString + targetString.slice(1);
  5474. }
  5475. return newTargetString;
  5476. };
  5477.  
  5478. /**
  5479. * 字符串首字母转小写
  5480. * @param {string} targetString 目标字符串
  5481. * @param {boolean} [otherStrToLowerCase=false] 剩余部分字符串转大写,默认false
  5482. * @returns
  5483. */
  5484. Utils.stringTitleToLowerCase = function (
  5485. targetString,
  5486. otherStrToUpperCase = false
  5487. ) {
  5488. let newTargetString = targetString.slice(0, 1).toLowerCase();
  5489. if (otherStrToUpperCase) {
  5490. newTargetString = newTargetString + targetString.slice(1).toUpperCase();
  5491. } else {
  5492. newTargetString = newTargetString + targetString.slice(1);
  5493. }
  5494. return newTargetString;
  5495. };
  5496.  
  5497. /**
  5498. * 字符串转Object对象,类似'{"test":""}' => {"test":""}
  5499. * @param {string} data
  5500. * @param {(error:Error)=>{}} errorCallBack 错误回调
  5501. * @returns {object}
  5502. * @example
  5503. * Utils.toJSON("{123:123}")
  5504. * > {123:123}
  5505. */
  5506. Utils.toJSON = function (data, errorCallBack = () => {}) {
  5507. let result = {};
  5508. if (typeof data === "object") {
  5509. return data;
  5510. }
  5511. Utils.tryCatch()
  5512. .config({ log: false })
  5513. .error((error) => {
  5514. Utils.tryCatch()
  5515. .error(() => {
  5516. try {
  5517. result = window.eval("(" + data + ")");
  5518. } catch (error) {
  5519. errorCallBack(error);
  5520. }
  5521. })
  5522. .run(() => {
  5523. if (
  5524. data &&
  5525. /^[\],:{}\s]*$/.test(
  5526. data
  5527. .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@")
  5528. .replace(
  5529. /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
  5530. "]"
  5531. )
  5532. .replace(/(?:^|:|,)(?:\s*\[)+/g, "")
  5533. )
  5534. ) {
  5535. result = new Function("return " + data)();
  5536. } else {
  5537. errorCallBack(new Error("target is not a JSON"));
  5538. }
  5539. });
  5540. })
  5541. .run(() => {
  5542. data = data.trim();
  5543. result = JSON.parse(data);
  5544. });
  5545. return result;
  5546. };
  5547.  
  5548. /**
  5549. * 提供一个封装了 try-catch 的函数,可以执行传入的函数并捕获其可能抛出的错误,并通过传入的错误处理函数进行处理。
  5550. * @returns {{run:function,config:function,error:function}} - 返回一个对象,其中包含 error 和 run 两个方法。
  5551. * @example
  5552. * Utils.tryCatch().error().run(()=>{console.log(1)});
  5553. * > 1
  5554. * @example
  5555. * Utils.tryCatch().config({log:true}).error((error)=>{console.log(error)}).run(()=>{throw new Error('测试错误')});
  5556. * > ()=>{throw new Error('测试错误')}出现错误
  5557. */
  5558. Utils.tryCatch = function () {
  5559. /* 定义变量和函数 */
  5560. let func = null;
  5561. let funcThis = null;
  5562. let handleErrorFunc = null;
  5563. let defaultDetails = {
  5564. log: true,
  5565. };
  5566. let funcArgs = arguments;
  5567. /**
  5568. * @function tryCatchObj
  5569. * @description 空函数,用于链式调用。
  5570. */
  5571. function tryCatchObj() {}
  5572.  
  5573. /**
  5574. * 配置
  5575. * @param {object} paramDetails
  5576. */
  5577. tryCatchObj.config = function (paramDetails) {
  5578. defaultDetails = Utils.assign(defaultDetails, paramDetails);
  5579. return tryCatchObj;
  5580. };
  5581. /**
  5582. * 设置错误处理函数。
  5583. * @param {function|string} handler - 错误处理函数,可以是 function 或者 string 类型。如果是 string 类型,则会被当做代码进行执行。
  5584. * @returns {function} - 返回 tryCatchObj 函数。
  5585. */
  5586. tryCatchObj.error = function (handler) {
  5587. handleErrorFunc = handler;
  5588. return tryCatchObj;
  5589. };
  5590.  
  5591. /**
  5592. * 执行传入的函数并捕获其可能抛出的错误,并通过传入的错误处理函数进行处理。
  5593. * @param {function|string} fn - 待执行函数,可以是 function 或者 string 类型。如果是 string 类型,则会被当做代码进行执行。
  5594. * @param {object|null} fnThis - 待执行函数的作用域,用于apply指定
  5595. * @returns {any|function} - 如果函数有返回值,则返回该返回值;否则返回 tryCatchObj 函数以支持链式调用。
  5596. * @throws {Error} - 如果传入参数不符合要求,则会抛出相应类型的错误。
  5597. */
  5598. tryCatchObj.run = function (fn, fnThis) {
  5599. func = fn;
  5600. funcThis = fnThis;
  5601. let result = executeTryCatch(func, handleErrorFunc, funcThis);
  5602. return result !== void 0 ? result : tryCatchObj;
  5603. };
  5604.  
  5605. /**
  5606. * 执行传入的函数并捕获其可能抛出的错误,并通过传入的错误处理函数进行处理。
  5607. * @param {function|string} func - 待执行函数,可以是 function 或者 string 类型。如果是 string 类型,则会被当做代码进行执行。
  5608. * @param {function|string|null} handleErrorFunc - 错误处理函数,可以是 function 或者 string 类型。如果是 string 类型,则会被当做代码进行执行。
  5609. * @param {object|null} funcThis - 待执行函数的作用域,用于apply指定
  5610. * @returns {any|undefined} - 如果函数有返回值,则返回该返回值;否则返回 undefined。
  5611. */
  5612. function executeTryCatch(func, handleErrorFunc, funcThis) {
  5613. let result = void 0;
  5614. try {
  5615. if (typeof func === "string") {
  5616. (function () {
  5617. eval(func);
  5618. }).apply(funcThis, funcArgs);
  5619. } else {
  5620. result = func.apply(funcThis, funcArgs);
  5621. }
  5622. } catch (error) {
  5623. if (defaultDetails.log) {
  5624. console.log(
  5625. `%c ${func?.name ? func?.name : func + "出现错误"} `,
  5626. "color: #f20000"
  5627. );
  5628. console.log(`%c 错误原因:${error}`, "color: #f20000");
  5629. console.trace(func);
  5630. }
  5631. if (handleErrorFunc) {
  5632. if (typeof handleErrorFunc === "string") {
  5633. result = function () {
  5634. return eval(handleErrorFunc);
  5635. }.apply(funcThis, [...funcArgs, error]);
  5636. } else {
  5637. result = handleErrorFunc.apply(funcThis, [...funcArgs, error]);
  5638. }
  5639. }
  5640. }
  5641. return result;
  5642. }
  5643.  
  5644. // 返回 tryCatchObj 函数
  5645. return tryCatchObj;
  5646. };
  5647.  
  5648. /**
  5649. * 数组去重,去除重复的值
  5650. * @param {[...any]} uniqueArrayData 需要去重的数组
  5651. * @param {[...any]} compareArrayData 用来比较的数组
  5652. * @param {(item1,item2)=>{}} compareFun 数组比较方法,如果值相同,去除该数据
  5653. * @returns {object} 返回去重完毕的数组
  5654. * @example
  5655. * Utils.uniqueArray([1,2,3],[1,2],(item,item2)=>{return item===item2 ? true:false});
  5656. * > [3]
  5657. *
  5658. * @example
  5659. * Utils.uniqueArray([1,2,3],[1,2]);
  5660. * > [3]
  5661. *
  5662. * @example
  5663. * Utils.uniqueArray([{"key":1,"value":2},{"key":2}],[{"key":1}],(item,item2)=>{return item["key"] === item2["key"] ? true:false});
  5664. * > [{"key": 2}]
  5665. **/
  5666. Utils.uniqueArray = function (
  5667. uniqueArrayData = [],
  5668. compareArrayData = [],
  5669. compareFun = (item, item2) => {
  5670. return item === item2;
  5671. }
  5672. ) {
  5673. return Array.from(uniqueArrayData).filter(
  5674. (item) =>
  5675. !Array.from(compareArrayData).some(function (item2) {
  5676. return compareFun(item, item2);
  5677. })
  5678. );
  5679. };
  5680.  
  5681. /**
  5682. * 等待函数数组全部执行完毕,注意,每个函数的顺序不是同步
  5683. * @async
  5684. * @param {[...any] | [...HTMLElement]} data 需要遍历的数组
  5685. * @param {function} handleFunc 对该数组进行操作的函数,该函数的参数为数组格式的参数,[数组下标,数组项]
  5686. * @example
  5687. * await Utils.waitArrayLoopToEnd([func,func,func],xxxFunction);
  5688. **/
  5689. Utils.waitArrayLoopToEnd = function (data, handleFunc) {
  5690. if (typeof handleFunc !== "function" && typeof handleFunc !== "string") {
  5691. throw new Error(
  5692. "Utils.waitArrayLoopToEnd 参数 handleDataFunction 必须为 function|string 类型"
  5693. );
  5694. }
  5695. return Promise.all(
  5696. Array.from(data).map(async (item, index) => {
  5697. await Utils.tryCatch(index, item).run(handleFunc);
  5698. })
  5699. );
  5700. };
  5701.  
  5702. /**
  5703. * 等待指定元素出现,支持多个selector
  5704. * @param {...string} nodeSelectors 一个或多个节点选择器,必须为字符串类型
  5705. * @returns {Promise<HTMLElement|HTMLElement[]>}
  5706. * @example
  5707. * Utils.waitNode("div.xxx").then( element =>{
  5708. * console.log(element); // div.xxx => HTMLElement
  5709. * })
  5710. * @example
  5711. * Utils.waitNode("div.xxx","a.xxx").then( (elementList)=>{
  5712. * console.log(elementList[0]); // div.xxx => HTMLElement
  5713. * console.log(elementList[1]); // a.xxx => HTMLElement
  5714. * })
  5715. */
  5716. Utils.waitNode = async function (...nodeSelectors) {
  5717. /* 检查每个参数是否为字符串类型 */
  5718. for (let nodeSelector of nodeSelectors) {
  5719. if (typeof nodeSelector !== "string") {
  5720. throw new Error("Utils.waitNode 参数必须都是 string 类型");
  5721. }
  5722. }
  5723.  
  5724. return new Promise((resolve) => {
  5725. /* 防止触发第二次回调 */
  5726. let isReturn = false;
  5727.  
  5728. /**
  5729. * 检查所有选择器是否匹配到节点
  5730. * @param {MutationObserver} observer
  5731. */
  5732. let checkNodes = (observer) => {
  5733. let isFind = true;
  5734. let selectNodeArray = [];
  5735. for (let selector of nodeSelectors) {
  5736. let element = document.querySelector(selector);
  5737. if (!element) {
  5738. /* 没找到,直接退出循环 */
  5739. isFind = false;
  5740. break;
  5741. }
  5742. selectNodeArray.push(element);
  5743. }
  5744. if (isFind) {
  5745. isReturn = true;
  5746. observer?.disconnect();
  5747. /* 如果只有一个选择器,那么返回数组中存储的第一个 */
  5748. if (selectNodeArray.length === 1) {
  5749. resolve(selectNodeArray[0]);
  5750. } else {
  5751. resolve(selectNodeArray);
  5752. }
  5753. }
  5754. };
  5755.  
  5756. /* 在函数开始时检查节点是否已经存在 */
  5757. checkNodes();
  5758.  
  5759. /* 监听 DOM 的变化,直到至少有一个节点被匹配到 */
  5760. Utils.mutationObserver(document.documentElement, {
  5761. config: { subtree: true, childList: true, attributes: true },
  5762. callback: (mutations, observer) => {
  5763. if (isReturn) {
  5764. return;
  5765. }
  5766. checkNodes(observer);
  5767. },
  5768. });
  5769. });
  5770. };
  5771.  
  5772. /**
  5773. * 在规定时间内,等待任意元素出现,支持多个selector,如果未出现,则关闭监听
  5774. * @param {string[]|string} [nodeSelectorsList=[]] 一个或多个节点选择器,必须为字符串类型
  5775. * @param {number} [maxTime=0] xx毫秒(ms)后关闭监听,默认0ms
  5776. * @returns {Promise<HTMLElement|HTMLElement[]|undefined>}
  5777. * @example
  5778. * Utils.waitNodeWithInterval("a.xxx",30000).then(element=>{
  5779. * console.log(element);
  5780. * })
  5781. * @example
  5782. * Utils.waitNodeWithInterval(["div.xxx","a.xxx"],30000).then(elementList=>{
  5783. * console.log(elementList[0]); // div.xxx => HTMLElement
  5784. * console.log(elementList[1]); // a.xxx => HTMLElement
  5785. * })
  5786. */
  5787. Utils.waitNodeWithInterval = async function (
  5788. nodeSelectorsList = [],
  5789. maxTime = 0
  5790. ) {
  5791. let nodeSelectors = [];
  5792. /* 检查每个参数是否为字符串类型 */
  5793. if (Array.isArray(nodeSelectorsList)) {
  5794. for (let nodeSelector of nodeSelectorsList) {
  5795. if (typeof nodeSelector !== "string") {
  5796. throw new Error(
  5797. "Utils.waitNodeWithInterval 参数nodeSelectorsList必须为 string[] 类型"
  5798. );
  5799. }
  5800. }
  5801. nodeSelectors = nodeSelectorsList;
  5802. } else {
  5803. nodeSelectors.push(nodeSelectorsList);
  5804. }
  5805.  
  5806. return new Promise((resolve) => {
  5807. /* 防止触发第二次回调 */
  5808. let isReturn = false;
  5809.  
  5810. /* 检查所有选择器是否匹配到节点 */
  5811. let checkNodes = (observer) => {
  5812. let isFind = true;
  5813. let selectNodeArray = [];
  5814. for (let selector of nodeSelectors) {
  5815. let element = document.querySelector(selector);
  5816. if (!element) {
  5817. /* 没找到,直接退出循环 */
  5818. isFind = false;
  5819. break;
  5820. }
  5821. selectNodeArray.push(element);
  5822. }
  5823. if (isFind) {
  5824. isReturn = true;
  5825. observer?.disconnect();
  5826. /* 如果只有一个选择器,那么返回数组中存储的第一个 */
  5827. if (selectNodeArray.length === 1) {
  5828. resolve(selectNodeArray[0]);
  5829. } else {
  5830. resolve(selectNodeArray);
  5831. }
  5832. }
  5833. };
  5834.  
  5835. /* 在函数开始时检查节点是否已经存在 */
  5836. checkNodes();
  5837.  
  5838. /* 监听 DOM 的变化,直到至少有一个节点被匹配到 */
  5839. let mutationObserver = Utils.mutationObserver(document.documentElement, {
  5840. config: { subtree: true, childList: true, attributes: true },
  5841. callback: (mutations, observer) => {
  5842. if (isReturn) {
  5843. return;
  5844. }
  5845. checkNodes(observer);
  5846. },
  5847. });
  5848. setTimeout(() => {
  5849. mutationObserver.disconnect();
  5850. }, maxTime);
  5851. });
  5852. };
  5853. /**
  5854. * 等待任意元素出现,支持多个selector
  5855. * @param {...any} nodeSelectors 一个或多个节点选择器,必须为字符串类型
  5856. * @returns {Promise<HTMLElement>}
  5857. * @example
  5858. * Utils.waitAnyNode("div.xxx","a.xxx").then( element =>{
  5859. * console.log(element); // a.xxx => HTMLElement
  5860. * })
  5861. */
  5862. Utils.waitAnyNode = async function (...nodeSelectors) {
  5863. /* 检查每个参数是否为字符串类型 */
  5864. for (let nodeSelector of nodeSelectors) {
  5865. if (typeof nodeSelector !== "string") {
  5866. throw new Error("Utils.waitNode 参数必须为 ...string 类型");
  5867. }
  5868. }
  5869.  
  5870. return new Promise((resolve) => {
  5871. /* 防止触发第二次回调 */
  5872. let isReturn = false;
  5873.  
  5874. /* 检查所有选择器是否存在任意匹配到元素 */
  5875. let checkNodes = (observer) => {
  5876. let selectNode = null;
  5877. for (let selector of nodeSelectors) {
  5878. selectNode = document.querySelector(selector);
  5879. if (selectNode) {
  5880. /* 找到,退出循环 */
  5881. break;
  5882. }
  5883. }
  5884. if (selectNode) {
  5885. isReturn = true;
  5886. observer?.disconnect();
  5887. resolve(selectNode);
  5888. }
  5889. };
  5890.  
  5891. /* 在函数开始时检查节点是否已经存在 */
  5892. checkNodes();
  5893.  
  5894. /* 监听 DOM 的变化,直到至少有一个节点被匹配到 */
  5895. Utils.mutationObserver(document.documentElement, {
  5896. config: { subtree: true, childList: true, attributes: true },
  5897. callback: (mutations, observer) => {
  5898. if (isReturn) {
  5899. return;
  5900. }
  5901. checkNodes(observer);
  5902. },
  5903. });
  5904. });
  5905. };
  5906.  
  5907. /**
  5908. * 等待指定元素出现,支持多个selector
  5909. * @param {...string} nodeSelectors
  5910. * @returns {Promise<NodeList|NodeList[]>} 当nodeSelectors为数组多个时,
  5911. * 返回如:[ NodeList, NodeList ],
  5912. * 当nodeSelectors为单个时,
  5913. * 返回如:NodeList。
  5914. * NodeList元素与页面存在强绑定,当已获取该NodeList,但是页面中却删除了,该元素在NodeList中会被自动删除
  5915. * @example
  5916. * Utils.waitNodeList("div.xxx").then( nodeList =>{
  5917. * console.log(nodeList) // div.xxx => NodeList
  5918. * })
  5919. * @example
  5920. * Utils.waitNodeList("div.xxx","a.xxx").then( nodeListArray =>{
  5921. * console.log(nodeListArray[0]) // div.xxx => NodeList
  5922. * console.log(nodeListArray[1]) // a.xxx => NodeList
  5923. * })
  5924. */
  5925. Utils.waitNodeList = async function (...nodeSelectors) {
  5926. /* 检查每个参数是否为字符串类型 */
  5927. for (let nodeSelector of nodeSelectors) {
  5928. if (typeof nodeSelector !== "string") {
  5929. throw new Error("Utils.waitNode 参数必须为 ...string 类型");
  5930. }
  5931. }
  5932.  
  5933. return new Promise((resolve) => {
  5934. /* 防止触发第二次回调 */
  5935. let isReturn = false;
  5936.  
  5937. /* 检查所有选择器是否匹配到节点 */
  5938. let checkNodes = (observer) => {
  5939. let isFind = true;
  5940. let selectNodes = [];
  5941. for (let selector of nodeSelectors) {
  5942. let nodeList = document.querySelectorAll(selector);
  5943. if (nodeList.length === 0) {
  5944. /* 没找到,直接退出循环 */
  5945. isFind = false;
  5946. break;
  5947. }
  5948. selectNodes.push(nodeList);
  5949. }
  5950. if (isFind) {
  5951. isReturn = true;
  5952. observer?.disconnect();
  5953. /* 如果只有一个选择器,那么返回第一个 */
  5954. if (selectNodes.length === 1) {
  5955. resolve(selectNodes[0]);
  5956. } else {
  5957. resolve(selectNodes);
  5958. }
  5959. }
  5960. };
  5961.  
  5962. /* 在函数开始时检查节点是否已经存在 */
  5963. checkNodes();
  5964.  
  5965. /* 监听 DOM 的变化,直到至少有一个节点被匹配到 */
  5966. Utils.mutationObserver(document.documentElement, {
  5967. config: { subtree: true, childList: true, attributes: true },
  5968. callback: (mutations, observer) => {
  5969. if (isReturn) {
  5970. return;
  5971. }
  5972. checkNodes(observer);
  5973. },
  5974. });
  5975. });
  5976. };
  5977.  
  5978. /**
  5979. * 等待任意元素出现,支持多个selector
  5980. * @param {...string} nodeSelectors
  5981. * @returns {Promise<NodeList>} 返回NodeList
  5982. * NodeList元素与页面存在强绑定,当已获取该NodeList,但是页面中却删除了,该元素在NodeList中会被自动删除
  5983. * @example
  5984. * Utils.waitAnyNodeList("div.xxx").then( nodeList =>{
  5985. * console.log(nodeList) // div.xxx => NodeList
  5986. * })
  5987. * @example
  5988. * Utils.waitAnyNodeList("div.xxx","a.xxx").then( nodeList =>{
  5989. * console.log(nodeList) // a.xxx => NodeList
  5990. * })
  5991. */
  5992. Utils.waitAnyNodeList = async function (...nodeSelectors) {
  5993. /* 检查每个参数是否为字符串类型 */
  5994. for (let nodeSelector of nodeSelectors) {
  5995. if (typeof nodeSelector !== "string") {
  5996. throw new Error("Utils.waitNode 参数必须为 ...string 类型");
  5997. }
  5998. }
  5999.  
  6000. return new Promise((resolve) => {
  6001. /* 防止触发第二次回调 */
  6002. let isReturn = false;
  6003.  
  6004. /* 检查所有选择器是否匹配到节点 */
  6005. let checkNodes = (observer) => {
  6006. let selectNodes = [];
  6007. for (let selector of nodeSelectors) {
  6008. selectNodes = document.querySelectorAll(selector);
  6009. if (selectNodes.length) {
  6010. /* 找到,退出循环 */
  6011. break;
  6012. }
  6013. }
  6014. if (selectNodes.length) {
  6015. isReturn = true;
  6016. observer?.disconnect();
  6017. resolve(selectNodes);
  6018. }
  6019. };
  6020.  
  6021. /* 在函数开始时检查节点是否已经存在 */
  6022. checkNodes();
  6023.  
  6024. /* 监听 DOM 的变化,直到至少有一个节点被匹配到 */
  6025. Utils.mutationObserver(document.documentElement, {
  6026. config: { subtree: true, childList: true, attributes: true },
  6027. callback: (mutations, observer) => {
  6028. if (isReturn) {
  6029. return;
  6030. }
  6031. checkNodes(observer);
  6032. },
  6033. });
  6034. });
  6035. };
  6036.  
  6037. /**
  6038. * 等待对象上的属性出现
  6039. * @param {object|()=>object} checkObj 检查的对象
  6040. * @param {string} checkPropertyName 检查的对象的属性名
  6041. * @returns {Promise<any>}
  6042. * @example
  6043. * await Utils.waitProperty(window,"test");
  6044. * console.log("test success set");
  6045. *
  6046. * window.test = 1;
  6047. * > "test success set"
  6048. *
  6049. */
  6050. Utils.waitProperty = async function (checkObj, checkPropertyName) {
  6051. return new Promise((resolve) => {
  6052. let obj = checkObj;
  6053. if (typeof checkObj === "function") {
  6054. obj = checkObj();
  6055. }
  6056. if (Object.hasOwnProperty.call(obj, checkPropertyName)) {
  6057. resolve(obj[checkPropertyName]);
  6058. } else {
  6059. Object.defineProperty(obj, checkPropertyName, {
  6060. set: function (value) {
  6061. try {
  6062. resolve(value);
  6063. } catch (error) {
  6064. console.error("Error setting property:", error);
  6065. }
  6066. },
  6067. });
  6068. }
  6069. });
  6070. };
  6071.  
  6072. /**
  6073. * 在规定时间内等待对象上的属性出现
  6074. * @param {object| ()=> object } checkObj 检查的对象
  6075. * @param {string| (obj: any)=>boolean } checkPropertyName 检查的对象的属性名
  6076. * @param {number} [intervalTimer=250] 检查间隔时间(ms),默认250ms
  6077. * @param {number} [maxTime=-1] 限制在多长时间内,默认-1(不限制时间)
  6078. * @returns {Promise<any|undefined>}
  6079. * @example
  6080. * await Utils.waitPropertyByInterval(window,"test");
  6081. * console.log("test success set");
  6082. */
  6083. Utils.waitPropertyByInterval = async function (
  6084. checkObj,
  6085. checkPropertyName,
  6086. intervalTimer = 250,
  6087. maxTime = -1
  6088. ) {
  6089. let isResolve = false;
  6090. return new Promise((resolve) => {
  6091. let interval = setInterval(() => {
  6092. let obj = checkObj;
  6093. if (typeof checkObj === "function") {
  6094. obj = checkObj();
  6095. }
  6096. if (
  6097. (typeof checkPropertyName === "function" && checkPropertyName(obj)) ||
  6098. Object.hasOwnProperty.call(obj, checkPropertyName)
  6099. ) {
  6100. isResolve = true;
  6101. clearInterval(interval);
  6102. resolve(obj[checkPropertyName]);
  6103. }
  6104. }, intervalTimer);
  6105. if (maxTime !== -1) {
  6106. setTimeout(() => {
  6107. if (!isResolve) {
  6108. clearInterval(interval);
  6109. resolve();
  6110. }
  6111. }, maxTime);
  6112. }
  6113. });
  6114. };
  6115.  
  6116. /**
  6117. * 在规定时间内等待元素上的__vue__属性或者__vue__属性上的某个值出现出现
  6118. * @param {HTMLElement|()=>HTMLElement } element 目标元素
  6119. * @param {string|(__vue__:object)=> boolean |undefined} propertyName vue上的属性名
  6120. * @param {number} [timer=250] 间隔时间(ms),默认250ms
  6121. * @param {number} [maxTime=-1] 限制在多长时间内,默认-1(不限制时间)
  6122. * @param {"__vue__"|string} vueName vue挂载的属性名
  6123. * @example
  6124. * await Utils.waitVueByInterval(
  6125. * function(){
  6126. * return document.querySelector("a.xx")
  6127. * },
  6128. * function(__vue__){
  6129. * return Boolean(__vue__.xxx == null);
  6130. * },
  6131. * 250,
  6132. * 10000,
  6133. * "__vue__"
  6134. * )
  6135. */
  6136. Utils.waitVueByInterval = async function (
  6137. element,
  6138. propertyName,
  6139. timer = 250,
  6140. maxTime = -1,
  6141. vueName = "__vue__"
  6142. ) {
  6143. if (element == null) {
  6144. throw new Error("Utils.waitVueByInterval 参数element 不能为空");
  6145. }
  6146. await Utils.waitPropertyByInterval(
  6147. element,
  6148. function (targetElement) {
  6149. if (typeof propertyName === "undefined") {
  6150. return vueName in targetElement;
  6151. } else {
  6152. if (vueName in targetElement) {
  6153. if (typeof propertyName === "string") {
  6154. return propertyName in targetElement[vueName];
  6155. } else {
  6156. /* Function */
  6157. return propertyName(targetElement[vueName]);
  6158. }
  6159. } else {
  6160. return false;
  6161. }
  6162. }
  6163. },
  6164. timer,
  6165. maxTime
  6166. );
  6167. };
  6168.  
  6169. /**
  6170. * 观察对象的set、get
  6171. * @param {object} target 观察的对象
  6172. * @param {string} propertyName 观察的对象的属性名
  6173. * @param {?(value)=>{}} getCallBack 触发get的回调,可以自定义返回特定值
  6174. * @param {?(value)=>{}} setCallBack 触发set的回调,参数为将要设置的value
  6175. * @example
  6176. * Utils.watchObject(window,"test",()=>{return 111;},(value)=>{console.log("test出现,值是",value)});
  6177. *
  6178. * window.test = 1;
  6179. * > test出现,值是 1
  6180. * console.log(window.test);
  6181. * > 111;
  6182. */
  6183. Utils.watchObject = function (
  6184. target,
  6185. propertyName,
  6186. getCallBack,
  6187. setCallBack
  6188. ) {
  6189. if (
  6190. typeof getCallBack !== "function" &&
  6191. typeof setCallBack !== "function"
  6192. ) {
  6193. return;
  6194. }
  6195.  
  6196. if (typeof getCallBack === "function") {
  6197. Object.defineProperty(target, propertyName, {
  6198. get() {
  6199. if (typeof getCallBack === "function") {
  6200. return getCallBack(value);
  6201. } else {
  6202. return target[propertyName];
  6203. }
  6204. },
  6205. });
  6206. } else if (typeof setCallBack === "function") {
  6207. Object.defineProperty(target, propertyName, {
  6208. set(value) {
  6209. if (typeof setCallBack === "function") {
  6210. setCallBack(value);
  6211. }
  6212. },
  6213. });
  6214. } else {
  6215. Object.defineProperty(target, propertyName, {
  6216. get() {
  6217. if (typeof getCallBack === "function") {
  6218. return getCallBack(value);
  6219. } else {
  6220. return target[propertyName];
  6221. }
  6222. },
  6223. set(value) {
  6224. if (typeof setCallBack === "function") {
  6225. setCallBack(value);
  6226. }
  6227. },
  6228. });
  6229. }
  6230. };
  6231.  
  6232. return Utils;
  6233. });