UNPKG

@whitesev/utils

Version:

一个常用的工具库

1,656 lines (1,635 loc) 129 kB
import { clearInterval as WorkerClearInterval, clearTimeout as WorkerClearTimeout, setInterval as WorkerSetInterval, setTimeout as WorkerSetTimeout, } from "worker-timers"; import { version } from "./../package.json"; import { ajaxHooker } from "./ajaxHooker/ajaxHooker.js"; import { AjaxHooker1_2_4 } from "./ajaxHooker/ajaxHooker1.2.4"; import { ColorConversion } from "./ColorConversion"; import { CommonUtil } from "./CommonUtil"; import { UtilsDictionary } from "./Dictionary"; import { domUtils } from "./DOMUtils"; import { GBKEncoder } from "./GBKEncoder"; import { Hooks } from "./Hooks"; import { Httpx } from "./Httpx"; import { indexedDB } from "./indexedDB"; import { LockFunction } from "./LockFunction"; import { Log } from "./Log"; import { ModuleRaid } from "./ModuleRaid"; import { Progress } from "./Progress"; import { TryCatch } from "./TryCatch"; import type { UtilsAjaxHookResult } from "./types/ajaxHooker"; import { type ArgsType, type JSTypeNames, type UtilsOwnObject } from "./types/global"; import type { ReactInstance } from "./types/React"; import type { WindowApiOption } from "./types/WindowApi"; import { GenerateUUID } from "./UtilsCommon"; import { UtilsGMCookie } from "./UtilsGMCookie"; import { GMMenu } from "./UtilsGMMenu"; import { Vue } from "./Vue"; import { WindowApi } from "./WindowApi"; class Utils { private windowApi: typeof WindowApi.prototype; constructor(option?: WindowApiOption) { this.windowApi = new WindowApi(option); } /** 版本号 */ version = version; /** * JSON数据从源端替换到目标端中,如果目标端存在该数据则替换,不添加,返回结果为目标端替换完毕的结果 * @param target 目标数据 * @param source 源数据 * @param isAdd 是否可以追加键,默认false * @example * Utils.assign({"1":1,"2":{"3":3}}, {"2":{"3":4}}); * > * { "1": 1, "2": { "3": 4 } } */ assign = CommonUtil.assign.bind(CommonUtil); /** * 异步替换字符串 * @param string 需要被替换的目标字符串 * @param pattern 正则匹配模型 * @param asyncFn 异步获取的函数 */ asyncReplaceAll( string: string, pattern: RegExp | string, asyncFn: (item: string) => Promise<string> ): Promise<string>; async asyncReplaceAll(string: string, pattern: RegExp | string, asyncFn: (item: string) => Promise<string>) { const that = this; if (typeof string !== "string") { throw new TypeError("string必须是字符串"); } if (typeof asyncFn !== "function") { throw new TypeError("asyncFn必须是函数"); } let reg; if (typeof pattern === "string") { reg = new RegExp(that.toRegExpStr(pattern), "g"); } else if (pattern instanceof RegExp) { if (!pattern.global) { throw new TypeError("pattern必须是全局匹配"); } reg = new RegExp(pattern); } else { throw new TypeError("pattern必须是正则对象"); } let result = []; let match; let lastIndex = 0; while ((match = reg.exec(string)) !== null) { /* 异步获取匹配对应的字符串 */ const item = asyncFn(match[0]); /* 获取该匹配项和上一个匹配项的中间的字符串 */ const prefix = string.slice(lastIndex, match.index); lastIndex = match.index + match[0].length; result.push(item); result.push(prefix); } result.push(string.slice(lastIndex)); /* 等待所有异步完成 */ result = await Promise.all(result); return result.join(""); } /** * ajax劫持库,支持xhr和fetch劫持。 * + 来源:https://bbs.tampermonkey.net.cn/thread-3284-1-1.html * + 作者:cxxjackie * + 版本:1.4.8 * + 旧版本:1.2.4 * + 文档:https://scriptcat.org/zh-CN/script-show-page/637/ * @param useOldVersion 是否使用旧版本,默认false */ ajaxHooker = (useOldVersion: boolean = false): UtilsAjaxHookResult => { if (useOldVersion) { return AjaxHooker1_2_4(); } else { return ajaxHooker(); } }; /** * 根据坐标点击canvas元素的内部位置 * @param canvasElement 画布元素 * @param clientX X坐标,默认值0 * @param clientY Y坐标,默认值0 * @param view 触发的事件目标 */ canvasClickByPosition( canvasElement: HTMLCanvasElement, clientX?: number | string, clientY?: number | string, view?: Window & typeof globalThis ): void; canvasClickByPosition(canvasElement: HTMLCanvasElement, clientX = 0, clientY = 0, view = this.windowApi.window) { if (!(canvasElement instanceof HTMLCanvasElement)) { throw new Error("Utils.canvasClickByPosition 参数canvasElement必须是canvas元素"); } clientX = parseInt(clientX.toString()); clientY = parseInt(clientY.toString()); const eventInit: MouseEventInit & { cancelBubble: boolean; } = { cancelBubble: true, cancelable: true, clientX: clientX, clientY: clientY, view: view, detail: 1, }; canvasElement.dispatchEvent(new MouseEvent("mousedown", eventInit)); canvasElement.dispatchEvent(new MouseEvent("mouseup", eventInit)); } /** * 复制formData数据 * @param formData 需要clone的数据 */ cloneFormData<T extends FormData>(formData: T, filterFn?: (key: string, value: string | Blob) => boolean): T { const clonedFormData = new FormData() as T; formData.forEach((value, key) => { const isFilter = typeof filterFn === "function" ? filterFn(key, value) : false; if (typeof isFilter === "boolean" && isFilter) { return; } clonedFormData.append(key, value); }); return clonedFormData; } /** * 函数重载实现 * @example * let getUsers = Utils.createOverload(); * getUsers.addImpl("",()=>{ * console.log("无参数"); * }); * * getUsers.addImpl("boolean",()=>{ * console.log("boolean"); * }); * * getUsers.addImpl("string",()=>{ * console.log("string"); * }); * * getUsers.addImpl("number","string",()=>{ * console.log("number string"); * }); */ createOverload(): { /** * 前面的参数都是字符串,最后一个参数是函数 */ addImpl<T extends JSTypeNames[]>(...args: [...T, (...args: ArgsType<T>) => any]): void; }; createOverload(): { /** * 前面的参数都是字符串,最后一个参数是函数 */ addImpl<T extends JSTypeNames[]>(...args: [...T, (...args: ArgsType<T>) => any]): void; } { const fnMap = new Map(); function overload(this: any, ...args: any[]) { const key = args.map((it) => typeof it).join(","); const fn = fnMap.get(key); if (!fn) { throw new TypeError("没有找到对应的实现"); } return fn.apply(this, args); } overload.addImpl = function (...args: any[]) { const fn = args.pop(); if (typeof fn !== "function") { throw new TypeError("最后一个参数必须是函数"); } const key = args.join(","); fnMap.set(key, fn); }; return overload; } /** * 颜色转换 * @returns */ ColorConversion = ColorConversion; /** * 深拷贝 * @param obj 对象 */ deepClone = CommonUtil.deepClone.bind(CommonUtil); /** * 防抖函数 * @param fn 需要触发的回调 * @param delay 防抖判定时间(毫秒),默认是0ms */ debounce<A extends any[], R>(fn: (...args: A) => R, delay?: number): (...args: A) => void; debounce<A extends any[], R>(fn: (...args: A) => R, delay = 0) { let timer: any = null as any; const that = this; return function (...args: A) { that.workerClearTimeout(timer); timer = that.workerSetTimeout(function () { fn.apply(that, args); }, delay); }; } /** * 字典 * @example * let dictionary = new Utils.Dictionary(); * let dictionary2 = new Utils.Dictionary(); * dictionary.set("test","111"); * dictionary.get("test"); * > '111' * dictionary.has("test"); * > true * dictionary.concat(dictionary2); **/ Dictionary = UtilsDictionary; /** * 下载base64格式的数据 * @param base64Data 需要转换的base64数据 * @param fileName 需要保存的文件名 * @param isIFrame (可选)是否使用iframe进行下载 * @example * Utils.downloadBase64("data:image/jpeg:base64/,xxxxxx"); **/ downloadBase64(base64Data: string, fileName: string, isIFrame?: boolean): void; downloadBase64(base64Data: string, fileName: string, isIFrame = false) { const that = this; if (typeof base64Data !== "string") { throw new Error("Utils.downloadBase64 参数 base64Data 必须为 string 类型"); } if (typeof fileName !== "string") { throw new Error("Utils.downloadBase64 参数 fileName 必须为 string 类型"); } if (isIFrame) { /* 使用iframe */ const $iframe = this.windowApi.document.createElement("iframe"); $iframe.style.display = "none"; $iframe.src = base64Data; (this.windowApi.document.body || this.windowApi.document.documentElement).appendChild($iframe); that.workerSetTimeout(() => { $iframe!.contentWindow!.document.execCommand("SaveAs", true, fileName); (this.windowApi.document.body || this.windowApi.document.documentElement).removeChild($iframe); }, 100); } else { /* 使用A标签 */ const linkElement = this.windowApi.document.createElement("a"); linkElement.setAttribute("target", "_blank"); linkElement.download = fileName; linkElement.href = base64Data; linkElement.click(); } } /** * 选中页面中的文字,类似Ctrl+F的选中 * @param str (可选)需要寻找的字符串,默认为空 * @param caseSensitive(可选)默认false * + true 区分大小写 * + false (默认) 不区分大小写 * @returns * + true 找到 * + false 未找到 * + undefined 不可使用该Api * @example * Utils.findWebPageVisibleText("xxxxx"); * > true **/ findWebPageVisibleText(str?: string, caseSensitive?: boolean): boolean | void; findWebPageVisibleText(str = "", caseSensitive = false) { let TRange = null; let strFound; if ((this.windowApi.globalThis as any).find) { /* CODE FOR BROWSERS THAT SUPPORT window.find */ const windowFind = (this.windowApi.self as any).find; strFound = windowFind(str, caseSensitive, true, true, false); if (strFound && this.windowApi.self.getSelection && !this.windowApi.self.getSelection()!.anchorNode) { strFound = windowFind(str, caseSensitive, true, true, false); } if (!strFound) { strFound = windowFind(str, 0, 1); while (windowFind(str, 0, 1)) continue; } } else if (navigator.appName.indexOf("Microsoft") != -1) { /* EXPLORER-SPECIFIC CODE */ if (TRange != null) { TRange = TRange as any; TRange.collapse(false); strFound = TRange.findText(str); if (strFound) TRange.select(); } if (TRange == null || strFound == 0) { TRange = (this.windowApi.self.document.body as any).createTextRange(); strFound = TRange.findText(str); if (strFound) TRange.select(); } } else if (navigator.appName == "Opera") { alert("Opera browsers not supported, sorry..."); return; } return strFound ? true : false; } /** * 格式化byte为KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB * @param byteSize 字节 * @param addType (可选)是否添加单位 * + true (默认) 添加单位 * + false 不添加单位 * @returns * + {string} 当addType为true时,且保留小数点末尾2位 * + {number} 当addType为false时,且保留小数点末尾2位 * @example * Utils.formatByteToSize("812304"); * > '793.27KB' * @example * Utils.formatByteToSize("812304",false); * > 793.27 **/ formatByteToSize(byteSize: number | string): number; formatByteToSize<T extends boolean>(byteSize: number | string, addType?: T): T extends true ? string : number; formatByteToSize(byteSize: number | string, addType = true) { byteSize = parseInt(byteSize.toString()); if (isNaN(byteSize)) { throw new Error("Utils.formatByteToSize 参数 byteSize 格式不正确"); } let result = 0; let resultType = "KB"; const sizeData: UtilsOwnObject<number> = {}; sizeData.B = 1; sizeData.KB = 1024; sizeData.MB = sizeData.KB * sizeData.KB; sizeData.GB = sizeData.MB * sizeData.KB; sizeData.TB = sizeData.GB * sizeData.KB; sizeData.PB = sizeData.TB * sizeData.KB; sizeData.EB = sizeData.PB * sizeData.KB; sizeData.ZB = sizeData.EB * sizeData.KB; sizeData.YB = sizeData.ZB * sizeData.KB; sizeData.BB = sizeData.YB * sizeData.KB; sizeData.NB = sizeData.BB * sizeData.KB; sizeData.DB = sizeData.NB * sizeData.KB; for (const key in sizeData) { result = byteSize / (sizeData as any)[key]; resultType = key; if (sizeData.KB >= result) { break; } } result = result.toFixed(2) as any; result = addType ? result + resultType.toString() : (parseFloat(result.toString()) as any); return result; } /** * 应用场景: 当你想要获取数组形式的元素时,它可能是其它的选择器,那么需要按照先后顺序填入参数 * 第一个是优先级最高的,依次下降,如果都没有,返回空列表 * 支持document.querySelectorAll、$("")、()=>{return document.querySelectorAll("")} * @param NodeList * @example * Utils.getNodeListValue( * document.querySelectorAll("div.xxx"), * document.querySelectorAll("a.xxx") * ); * > [...div,div,div] * @example * Utils.getNodeListValue(divGetFunction,aGetFunction); * > [...div,div,div] */ getNodeListValue(...args: (NodeList | (() => HTMLElement))[]): HTMLElement[]; getNodeListValue(...args: (NodeList | (() => HTMLElement))[]) { let resultArray: HTMLElement[] = []; for (const arg of args) { let value = arg as any; if (typeof arg === "function") { /* 方法 */ value = arg(); } if (value.length !== 0) { resultArray = [...value]; break; } } return resultArray; } /** * 自动判断N个参数,获取非空的值,如果都是空,返回最后一个值 */ getNonNullValue(...args: any[]): any; getNonNullValue(...args: any[]) { let resultValue = args[args.length - 1]; const that = this; for (const argValue of args) { if (that.isNotNull(argValue)) { resultValue = argValue; break; } } return resultValue; } /** * 获取格式化后的时间 * @param text (可选)需要格式化的字符串或者时间戳,默认:new Date() * @param formatType (可选)格式化成的显示类型,默认:yyyy-MM-dd HH:mm:ss * + yyyy 年 * + MM 月 * + dd 天 * + HH 时 (24小时制) * + hh 时 (12小时制) * + mm 分 * + ss 秒 * @returns {string} 返回格式化后的时间 * @example * Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss"); * > '23:59:00' * @example * Utils.formatTime(1899187424988,"HH:mm:ss"); * > '15:10:13' * @example * Utils.formatTime() * > '2023-1-1 00:00:00' **/ formatTime(text?: string | number | Date, formatType?: string): string; /** * 获取格式化后的时间 * @param text (可选)需要格式化的字符串或者时间戳,默认:new Date() * @param formatType (可选)格式化成的显示类型,默认:yyyy-MM-dd HH:mm:ss * + yyyy 年 * + MM 月 * + dd 天 * + HH 时 (24小时制) * + hh 时 (12小时制) * + mm 分 * + ss 秒 * @returns {string} 返回格式化后的时间 * @example * Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss"); * > '23:59:00' * @example * Utils.formatTime(1899187424988,"HH:mm:ss"); * > '15:10:13' * @example * Utils.formatTime() * > '2023-1-1 00:00:00' **/ formatTime( text?: string | number | Date, formatType?: | "yyyy-MM-dd HH:mm:ss" | "yyyy/MM/dd HH:mm:ss" | "yyyy_MM_dd_HH_mm_ss" | "yyyy年MM月dd日 HH时mm分ss秒" | "yyyy年MM月dd日 hh:mm:ss" | "yyyy年MM月dd日 HH:mm:ss" | "yyyy-MM-dd" | "yyyyMMdd" | "HH:mm:ss" | "yyyy" | "MM" | "dd" | "HH" | "mm" | "ss" ): string; formatTime(text = new Date(), formatType = "yyyy-MM-dd HH:mm:ss") { const time = text == null ? new Date() : new Date(text); /** * 校验时间补0 * @param timeNum * @returns */ function checkTime(timeNum: number) { if (timeNum < 10) return `0${timeNum}`; return timeNum; } /** * 时间制修改 24小时制转12小时制 * @param hourNum 小时 * @returns */ function timeSystemChange(hourNum: number) { return hourNum > 12 ? hourNum - 12 : hourNum; } const timeRegexp = { yyyy: time.getFullYear(), /* 年 */ MM: checkTime(time.getMonth() + 1), /* 月 */ dd: checkTime(time.getDate()), /* 日 */ HH: checkTime(time.getHours()), /* 时 (24小时制) */ hh: checkTime(timeSystemChange(time.getHours())), /* 时 (12小时制) */ mm: checkTime(time.getMinutes()), /* 分 */ ss: checkTime(time.getSeconds()), /* 秒 */ }; Object.keys(timeRegexp).forEach(function (key) { const replaecRegexp = new RegExp(key, "g"); formatType = formatType.replace(replaecRegexp, (timeRegexp as any)[key]); }); return formatType; } /** * 字符串格式的时间转时间戳 * @param text 字符串格式的时间,例如: * + 2022-11-21 00:00:00 * + 00:00:00 * @returns 返回时间戳 * @example * Utils.formatToTimeStamp("2022-11-21 00:00:00"); * > 1668960000000 **/ formatToTimeStamp(text: string): number; formatToTimeStamp(text: string) { /* 把字符串格式的时间(完整,包括日期和时间)格式化成时间 */ if (typeof text !== "string") { throw new Error("Utils.formatToTimeStamp 参数 text 必须为 string 类型"); } if (text.length === 8) { /* 该字符串只有时分秒 */ const today = new Date(); text = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()} ${text}`; } text = text.substring(0, 19); text = text.replace(/-/g, "/"); const timestamp = new Date(text).getTime(); return timestamp; } /** * gbk格式的url编码,来自https://greasyfork.org/zh-CN/scripts/427726-gbk-url-js * @example * let gbkEncoder = new Utils.GBKEncoder(); * gbkEncoder.encode("测试"); * > '%B2%E2%CA%D4' * gbkEncoder.decode("%B2%E2%CA%D4"); * > 测试 */ GBKEncoder = GBKEncoder; /** * 获取NodeList或Array对象中的最后一个的值 * @param targetObj * @returns * @example * Utils.getArrayLastValue(document.querySelectorAll("div")); * > div * @example * Utils.getArrayLastValue([1,2,3,4,5]); * > 5 */ getArrayLastValue<R = unknown>(targetObj: NodeList | any[]): R; getArrayLastValue(target: NodeList | any[]) { return target[target.length - 1]; } /** * 应用场景: 当想获取的元素可能是不同的选择器的时候,按顺序优先级获取 * 参数类型可以是Element或者是Function * @returns 如果都没有的话,返回null * @example * // 如果a.aaa不存在的话,取a.bbb,这里假设a.aaa不存在 * Utils.getArrayRealValue(document.querySelector("a.aaa"),document.querySelector("a.bbb")); * > a.bbb * @example * Utils.getArrayRealValue(()=>{return document.querySelector("a.aaa").href},()=>{document.querySelector("a.bbb").getAttribute("data-href")}); * > javascript:; */ getArrayRealValue(...args: (NodeList | (() => HTMLElement))[]): any; getArrayRealValue(...args: (NodeList | (() => HTMLElement))[]) { let result = null; for (let arg of args) { if (typeof arg === "function") { /* 方法 */ (<any>arg) = arg(); } if (arg != null) { result = arg; break; } } return result; } /** * 获取天数差异,如何获取某个时间与另一个时间相差的天数 * @param timestamp1 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now() * @param timestamp2 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now() * @param type (可选)返回的数字的表达的类型,比如:年、月、天、时、分、秒、auto,默认天 * @example * Utils.getDaysDifference(new Date().getTime()); * > 0 * @example * Utils.getDaysDifference(new Date().getTime(),undefined,"秒"); * > 0 */ getDaysDifference(timestamp1?: number, timestamp2?: number, type?: "auto"): string; /** * 获取天数差异,如何获取某个时间与另一个时间相差的天数 * @param timestamp1 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now() * @param timestamp2 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now() * @param type (可选)返回的数字的表达的类型,比如:年、月、天、时、分、秒、auto,默认天 * @example * Utils.getDaysDifference(new Date().getTime()); * > 0 * @example * Utils.getDaysDifference(new Date().getTime(),undefined,"秒"); * > 0 */ getDaysDifference(timestamp1?: number, timestamp2?: number, type?: "年" | "月" | "天" | "时" | "分" | "秒"): number; getDaysDifference(timestamp1 = Date.now(), timestamp2 = Date.now(), type = "天"): number | string { type = type.trim(); if (timestamp1.toString().length === 10) { timestamp1 = timestamp1 * 1000; } if (timestamp2.toString().length === 10) { timestamp2 = timestamp2 * 1000; } const smallTimeStamp = timestamp1 > timestamp2 ? timestamp2 : timestamp1; const bigTimeStamp = timestamp1 > timestamp2 ? timestamp1 : timestamp2; const oneSecond = 1000; /* 一秒的毫秒数 */ const oneMinute = 60 * oneSecond; /* 一分钟的毫秒数 */ const oneHour = 60 * oneMinute; /* 一小时的毫秒数 */ const oneDay = 24 * oneHour; /* 一天的毫秒数 */ const oneMonth = 30 * oneDay; /* 一个月的毫秒数(30天) */ const oneYear = 12 * oneMonth; /* 一年的毫秒数 */ const bigDate = new Date(bigTimeStamp); const smallDate = new Date(smallTimeStamp); let remainderValue = 1; if (type === "年") { remainderValue = oneYear; } else if (type === "月") { remainderValue = oneMonth; } else if (type === "天") { remainderValue = oneDay; } else if (type === "时") { remainderValue = oneHour; } else if (type === "分") { remainderValue = oneMinute; } else if (type === "秒") { remainderValue = oneSecond; } let diffValue = Math.round(Math.abs(((bigDate as any) - (smallDate as any)) / remainderValue)); if (type === "auto") { const timeDifference = bigTimeStamp - smallTimeStamp; diffValue = Math.floor(timeDifference / (24 * 3600 * 1000)); if (diffValue > 0) { (diffValue as any) = `${diffValue}天`; } else { /* 计算出小时数 */ const leave1 = timeDifference % (24 * 3600 * 1000); /* 计算天数后剩余的毫秒数 */ const hours = Math.floor(leave1 / (3600 * 1000)); if (hours > 0) { (diffValue as any) = `${hours}小时`; } else { /* 计算相差分钟数 */ const leave2 = leave1 % (3600 * 1000); /* 计算小时数后剩余的毫秒数 */ const minutes = Math.floor(leave2 / (60 * 1000)); if (minutes > 0) { (diffValue as any) = `${minutes}分钟`; } else { /* 计算相差秒数 */ const leave3 = leave2 % (60 * 1000); /* 计算分钟数后剩余的毫秒数 */ const seconds = Math.round(leave3 / 1000); (diffValue as any) = `${seconds}秒`; } } } } return diffValue; } /** * 获取最大值 * @example * Utils.getMaxValue(1,3,5,7,9) * > 9 */ getMaxValue(...args: number[]): number; /** * 获取最大值 * @example * Utils.getMaxValue([1,3,5]) * > 5 */ getMaxValue(val: number[]): number; /** * 获取最大值 * @example * Utils.getMaxValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)}) * > 456 */ getMaxValue(val: UtilsOwnObject<number>, handler: (key: any, value: any) => number): number; /** * 获取最大值 * @example * Utils.getMaxValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)}) * > 2 */ getMaxValue(...args: any[]): number { const result = [...args]; let newResult: number[] = []; if (result.length === 0) { return void 0 as any as number; } if (result.length > 1) { if (result.length === 2 && typeof result[0] === "object" && typeof result[1] === "function") { const data = result[0]; const handleDataFunc = result[1]; Object.keys(data).forEach((keyName) => { newResult = [...newResult, handleDataFunc(keyName, data[keyName])]; }); } else { result.forEach((item) => { if (!isNaN(parseFloat(item))) { newResult = [...newResult, parseFloat(item)]; } }); } return Math.max(...newResult); } else { result[0].forEach((item: any) => { if (!isNaN(parseFloat(item))) { newResult = [...newResult, parseFloat(item)]; } }); return Math.max(...newResult); } } /** * 获取页面中最大的z-index的元素信息 * @param deviation 获取最大的z-index值的偏移,默认是1 * @param node 进行判断的元素,默认是document * @param ignoreCallBack 执行元素处理时调用的函数,返回false可忽略不想要处理的元素 * @example * Utils.getMaxZIndexNodeInfo(); * > { * node: ..., * zIndex: 1001 * } **/ getMaxZIndexNodeInfo( deviation?: number, target?: Element | ShadowRoot | Document, ignoreCallBack?: ($ele: Element | HTMLElement | ShadowRoot) => boolean | void ): { node: Element; zIndex: number; }; getMaxZIndexNodeInfo( deviation = 1, target: Element | ShadowRoot | Document = this.windowApi.document, ignoreCallBack?: ($ele: Element | HTMLElement | ShadowRoot) => boolean | void ): { node: Element; zIndex: number; } { deviation = Number.isNaN(deviation) ? 1 : deviation; const that = this; // 最大值 2147483647 // const maxZIndex = Math.pow(2, 31) - 1; // 比较值 2000000000 const maxZIndexCompare = 2 * Math.pow(10, 9); // 当前页面最大的z-index let zIndex = 0; // 当前的最大z-index的元素,调试使用 let maxZIndexNode: Element | null = null; /** * 元素是否可见 * @param $css */ function isVisibleNode($css: CSSStyleDeclaration): boolean { return $css.position !== "static" && $css.display !== "none"; } /** * 查询元素的z-index * 并比较值是否是已获取的最大值 * @param $ele */ function queryMaxZIndex($ele: Element) { if (typeof ignoreCallBack === "function") { const ignoreResult = ignoreCallBack($ele); if (typeof ignoreResult === "boolean" && !ignoreResult) { return; } } /** 元素的样式 */ const nodeStyle = that.windowApi.window.getComputedStyle($ele); /* 不对position为static和display为none的元素进行获取它们的z-index */ if (isVisibleNode(nodeStyle)) { const nodeZIndex = parseInt(nodeStyle.zIndex); if (!isNaN(nodeZIndex)) { if (nodeZIndex > zIndex) { // 赋值到全局 zIndex = nodeZIndex; maxZIndexNode = $ele; } } // 判断shadowRoot if ($ele.shadowRoot != null && $ele instanceof ShadowRoot) { $ele.shadowRoot.querySelectorAll("*").forEach(($shadowEle) => { queryMaxZIndex($shadowEle); }); } } } target.querySelectorAll("*").forEach(($ele) => { queryMaxZIndex($ele); }); zIndex += deviation; if (zIndex >= maxZIndexCompare) { // 最好不要超过最大值 zIndex = maxZIndexCompare; } return { node: maxZIndexNode!, zIndex: zIndex, }; } /** * 获取页面中最大的z-index * @param deviation 获取最大的z-index值的偏移,默认是1 * @param node 进行判断的元素,默认是document * @param ignoreCallBack 执行元素处理时调用的函数,返回false可忽略不想要处理的元素 * @example * Utils.getMaxZIndex(); * > 1001 **/ getMaxZIndex( deviation?: number, target?: Element | DocumentOrShadowRoot | Document, ignoreCallBack?: ($ele: Element | HTMLElement | ShadowRoot) => boolean | void ): number; getMaxZIndex( deviation = 1, target: Element | ShadowRoot | Document = this.windowApi.document, ignoreCallBack?: ($ele: Element | HTMLElement | ShadowRoot) => boolean | void ): number { return this.getMaxZIndexNodeInfo(deviation, target, ignoreCallBack).zIndex; } /** * 获取最小值 * @example * Utils.getMinValue(1,3,5,7,9) * > 1 */ getMinValue(...args: number[]): number; /** * 获取最小值 * @example * Utils.getMinValue([1,3,5]) * > 1 */ getMinValue(val: number[]): number; /** * 获取最小值 * @example * Utils.getMinValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)}) * > 123 */ getMinValue(val: UtilsOwnObject<number>, handler: (key: any, value: any) => number): number; /** * 获取最小值 * @example * Utils.getMinValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)}) * > 0 */ getMinValue(val: UtilsOwnObject<number>[], handler: (index: number, value: any) => number): number; getMinValue(...args: any[]): number { const result = [...args]; let newResult: number[] = []; if (result.length === 0) { return void 0 as any as number; } if (result.length > 1) { if (result.length === 2 && typeof result[0] === "object" && typeof result[1] === "function") { const data = result[0]; const handleDataFunc = result[1]; Object.keys(data).forEach((keyName) => { newResult = [...newResult, handleDataFunc(keyName, data[keyName])]; }); } else { result.forEach((item) => { if (!isNaN(parseFloat(item))) { newResult = [...newResult, parseFloat(item)]; } }); } return Math.min(...newResult); } else { result[0].forEach((item: any) => { if (!isNaN(parseFloat(item))) { newResult = [...newResult, parseFloat(item)]; } }); return Math.min(...newResult); } } /** * 获取随机的安卓手机User-Agent * @example * Utils.getRandomAndroidUA(); * > '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' **/ getRandomAndroidUA(): string { const that = this; const mobileNameList = [ "LDN-LX3", "RNE-L03", "ASUS_X00ID Build/NMF26F", "WAS-LX3", "PRA-LX3", "MYA-L03", "Moto G Play", "Moto C Build/NRD90M.063", "Redmi Note 4 Build/NRD90M", "HUAWEI VNS-L21 Build/HUAWEIVNS-L21", "VTR-L09", "TRT-LX3", "M2003J15SC Build/RP1A.200720.011; wv", "MI 13 Build/OPR1.170623.027; wv", ]; /* 安卓版本 */ const androidVersion = that.getRandomValue(12, 14); /* 手机型号 */ const randomMobile = that.getRandomValue(mobileNameList); /* chrome大版本号 */ const chromeVersion1 = that.getRandomValue(130, 140); const chromeVersion2 = that.getRandomValue(0, 0); const chromeVersion3 = that.getRandomValue(2272, 6099); const chromeVersion4 = that.getRandomValue(1, 218); return `Mozilla/5.0 (Linux; Android ${androidVersion}; ${randomMobile}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.${chromeVersion2}.${chromeVersion3}.${chromeVersion4} Mobile Safari/537.36`; } /** * 获取随机的电脑端User-Agent * + Mozilla/5.0:以前用于Netscape浏览器,目前大多数浏览器UA都会带有 * + Windows NT 13:代表Window11系统 * + Windows NT 10.0:代表Window10系统 * + Windows NT 6.1:代表windows7系统 * + WOW64:Windows-on-Windows 64-bit,32位的应用程序运行于此64位处理器上 * + Win64:64位 * + AppleWebKit/537.36:浏览器内核 * + KHTML:HTML排版引擎 * + like Gecko:这不是Geckeo 浏览器,但是运行起来像Geckeo浏览器 * + Chrome/106.0.5068.19:Chrome版本号 * + Safari/537.36:宣称自己是Safari? * @returns 返回随机字符串 * @example * Utils.getRandomPCUA(); * > 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5068.19 Safari/537.36' **/ getRandomPCUA(): string { const that = this; /* chrome大版本号 */ const chromeVersion1 = that.getRandomValue(130, 140); const chromeVersion2 = that.getRandomValue(0, 0); const chromeVersion3 = that.getRandomValue(2272, 6099); const chromeVersion4 = that.getRandomValue(1, 218); return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.${chromeVersion2}.${chromeVersion3}.${chromeVersion4} Safari/537.36`; } /** * 获取随机值 * @example * Utils.getRandomValue(1,9,6,99) * > 6 */ getRandomValue<T>(...args: T[]): T; /** * 获取随机值 * @example * Utils.getRandomValue([1,2,3]) * > 3 * @example * Utils.getRandomValue({1:"结果1",2:"结果2",3:"结果3"}}) * > 结果2 */ getRandomValue<T>(val: T[] | UtilsOwnObject<T>): T; /** * 获取两个数之间随机值 * @example * Utils.getRandomValue(1,9) * > 6 */ getRandomValue(val_1: number, val_2: number): number; /** * 获取随机值 * @example * Utils.getRandomValue({1:1},{2:2}) * > {1: 1} */ getRandomValue<T>(val_1: UtilsOwnObject<T>, val_2: UtilsOwnObject<T>): T; getRandomValue(...args: any[]): any { const result = [...args]; if (result.length > 1) { if (result.length === 2 && typeof result[0] === "number" && typeof result[1] === "number") { const leftNumber = result[0] > result[1] ? result[1] : result[0]; const rightNumber = result[0] > result[1] ? result[0] : result[1]; return Math.round(Math.random() * (rightNumber - leftNumber)) + leftNumber; } else { return result[Math.floor(Math.random() * result.length)]; } } else if (result.length === 1) { const paramData = result[0]; if (Array.isArray(paramData)) { return paramData[Math.floor(Math.random() * paramData.length)]; } else if (typeof paramData === "object" && Object.keys(paramData).length > 0) { const paramObjDataKey = Object.keys(paramData)[Math.floor(Math.random() * Object.keys(paramData).length)]; return paramData[paramObjDataKey]; } else { return paramData; } } } /** * 获取元素上的使用React框架的实例属性,目前包括reactFiber、reactProps、reactEvents、reactEventHandlers、reactInternalInstance * @param element 需要获取的目标元素 * @returns * @example * Utils.getReactInstance(document.querySelector("input"))?.reactProps?.onChange({target:{value:"123"}}); */ getReactInstance(element: HTMLElement | Element): ReactInstance { const result = {}; if (element == null) { return result; } const keys = Object.keys(element); keys.forEach((domPropsName) => { if (domPropsName.startsWith("__react")) { const propsName = domPropsName.replace(/__(.+)\$.+/i, "$1"); const propsValue = Reflect.get(element, domPropsName); if (propsName in result) { console.error(`重复属性 ${domPropsName}`); } else { Reflect.set(result, propsName, propsValue); } } }); return result; } /** * 获取对象上的Symbol属性,如果没设置keyName,那么返回一个对象,对象是所有遍历到的Symbol对象 * @param target 目标对象 * @param keyName (可选)Symbol名或者Symbol对象 */ getSymbol(target: any, keyName?: string | symbol) { if (typeof target !== "object") { throw new TypeError("target不是一个对象"); } const objectsSymbols = Object.getOwnPropertySymbols(target); if (typeof keyName === "string") { const findSymbol = objectsSymbols.find((key) => { return key.toString() === keyName; }); if (findSymbol) { return target[findSymbol]; } } else if (typeof keyName === "symbol") { const findSymbol = objectsSymbols.find((key) => { return key === keyName; }); if (findSymbol) { return (target as any)[findSymbol]; } } else { const result = {}; objectsSymbols.forEach((item) => { (result as any)[item] = target[item]; }); return result; } } /** * 获取文本的字符长度 * @param text * @example * Utils.getTextLength("测试文本") * > 12 */ getTextLength(text: string): number { const encoder = new TextEncoder(); const bytes = encoder.encode(text); return bytes.length; } /** * 获取文本占据的空间大小,返回自动的单位,如12 Kb,14 K,20 MB,1 GB * @param text 目标字符串 * @param addType (可选)是否添加单位 * + true (默认) 自动添加单位 * + false 不添加单位 * @example * Utils.getTextStorageSize("测试文本"); * > '12.00B' */ getTextStorageSize<T extends boolean>(text: string, addType?: T): T extends true ? string : number; getTextStorageSize(text: string, addType = true) { const that = this; return that.formatByteToSize(that.getTextLength(text), addType); } /** * 获取迅雷协议的Url * @param url Url链接或者其它信息 */ getThunderUrl(url: string): string; getThunderUrl(url: string): string { if (url == null) { throw new TypeError("url不能为空"); } if (typeof url !== "string") { throw new TypeError("url必须是string类型"); } if (url.trim() === "") { throw new TypeError("url不能为空字符串或纯空格"); } return `thunder://${this.windowApi.globalThis.btoa(`AA${url}ZZ`)}`; } /** * 对于GM_cookie的兼容写法,当无法使用GM_cookie时可以使用这个,但是并不完全兼容,有些写不出来且限制了httponly是无法访问的 * @example let GM_cookie = new Utils.GM_Cookie(); GM_cookie.list({name:"xxx_cookie_xxx"},function(cookies,error){ if (!error) { console.log(cookies); console.log(cookies.value); } else { console.error(error); } }); GM_cookie.set({name:"xxx_cookie_test_xxx",value:"这是Cookie测试值"},function(error){ if (error) { console.error(error); } else { console.log('Cookie set successfully.'); } }) GM_cookie.delete({name:"xxx_cookie_test_xxx"},function(error){ if (error) { console.error(error); } else { console.log('Cookie set successfully.'); } }) **/ GM_Cookie = UtilsGMCookie; /** * 注册油猴菜单,要求本地存储的键名不能存在其它键名`GM_Menu_Local_Map`会冲突/覆盖 * @example let GM_Menu = new Utils.GM_Menu({ data: [ { menu_key: "menu_key", text: "测试按钮", enable: true, accessKey: "a", autoClose: false, showText(text, enable) { return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data) { console.log("点击菜单,值修改为", data.enable); }, }, ], autoReload: false, GM_getValue, GM_setValue, GM_registerMenuCommand, GM_unregisterMenuCommand, }); // 获取某个菜单项的值 GM_Menu.get("menu_key"); > true // 获取某个菜单项的开启/关闭后显示的文本 GM_Menu.getShowTextValue("menu_key"); > √测试按钮 // 添加键为menu_key2的菜单项 GM_Menu.add({ key:"menu_key2", text: "测试按钮2", enable: false, showText(text,enable){ return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data){ console.log("点击菜单,值修改为",data.enable); } }); // 使用数组的方式添加多个菜单,如menu_key3、menu_key4 GM_Menu.add([ { key:"menu_key3", text: "测试按钮3", enable: false, showText(text,enable){ return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data){ console.log("点击菜单,值修改为",data.enable); } }, { key:"menu_key4", text: "测试按钮4", enable: false, showText(text,enable){ return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data){ console.log("点击菜单,值修改为",data.enable); } } ]); // 更新键为menu_key的显示文字和点击回调 GM_Menu.update({ menu_key:{ text: "更新后的测试按钮", enable: true, showText(text,enable){ return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data){ console.log("点击菜单更新后的测试按钮,新值修改为",data.enable); } } }); // 删除键为menu_key的菜单 GM_Menu.delete("menu_key"); **/ GM_Menu = GMMenu; /** * 基于Function prototype,能够勾住和释放任何函数 * * .hook * + realFunc {string} 用于保存原始函数的函数名称,用于unHook * + hookFunc {string} 替换的hook函数 * + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1 * + methodName {string} 匿名函数需显式传入目标函数名eg:this.Begin = function(){....};} * * .unhook * + realFunc {string} 用于保存原始函数的函数名称,用于unHook * + funcName {string} 被Hook的函数名称 * + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1 * @example let hook = new Utils.Hooks(); hook.initEnv(); function myFunction(){ console.log("我自己需要执行的函数"); } function testFunction(){ console.log("正常执行的函数"); } testFunction.hook(testFunction,myFunction,window); **/ Hooks = Hooks; /** * 为减少代码量和回调,把GM_xmlhttpRequest封装 * 文档地址: https://www.tampermonkey.net/documentation.php?ext=iikm * 其中onloadstart、onprogress、onreadystatechange是回调形式,onabort、ontimeout、onerror可以设置全局回调函数 * @param _GM_xmlHttpRequest_ 油猴中的GM_xmlhttpRequest * @example let httpx = new Utils.Httpx(GM_xmlhttpRequest); let postResp = await httpx.post({ url:url, data:JSON.stringify({ test:1 }), timeout: 5000 }); console.log(postResp); > { status: true, data: {responseText: "...", response: xxx,...}, msg: "请求完毕", type: "onload", } if(postResp === "onload" && postResp.status){ // onload }else if(postResp === "ontimeout"){ // ontimeout } * @example // 也可以先配置全局参数 let httpx = new Utils.Httpx(GM_xmlhttpRequest); httpx.config({ timeout: 5000, async: false, responseType: "html", redirect: "follow", }) // 优先级为 默认details < 全局details < 单独的details */ Httpx = Httpx; /** * 浏览器端的indexedDB操作封装 * @example let db = new Utils.indexedDB('web_DB', 'nav_text') let data = {name:'管理员', roleId: 1, type: 1}; db.save('list',data).then((resolve)=>{ console.log(resolve,'存储成功') }) db.get('list').then((resolve)=>{ console.log(resolve,'查询成功') }) db.getPaging('list',20,10).then((resolve)=>{ console.log(resolve,'查询分页偏移第20,一共10行成功'); }) db.delete('list').then(resolve=>{ console.log(resolve,'删除成功---->>>>>>name') }) db.deleteAll().then(resolve=>{ console.log(resolve,'清除数据库---->>>>>>name') }) **/ indexedDB = indexedDB; /** * 判断目标函数是否是Native Code * @param target * @returns * + true 是Native * + false 不是Native * @example * Utils.isNativeFunc(window.location.assign) * > true */ isNativeFunc(target: (...args: any[]) => any): boolean; isNativeFunc(target: (...args: any[]) => any): boolean { return Boolean(target.toString().match(/^function .*\(\) { \[native code\] }$/)); } /** * 判断当前的位置是否位于页面底部附近 * @param nearBottomHeight (可选)判断在底部的误差值,默认:50 * @returns * + true 在底部附近 * + false 不在底部附近 */ isNearBottom(nearBottomHeight?: number): boolean; /** * 判断元素内当前的位置是否位于元素内底部附近 * @param target 需要判断的元素 * @param nearBottomHeight (可选)判断在底部的误差值,默认:50 */ isNearBottom(target: HTMLElement, nearBottomHeight?: number): boolean; isNearBottom(...args: any[]): boolean { let nearBottomHeight = 50; const checkWindow = () => { // 已滚动的距离 const scrollTop: number = this.windowApi.window.pageYOffset || this.windowApi.document.documentElement.scrollTop; // 视窗高度 const viewportHeight: number = this.windowApi.window.innerHeight || this.windowApi.document.documentElement.clientHeight; // 最大滚动距离 const maxScrollHeight: number = this.windowApi.document.documentElement.scrollHeight - nearBottomHeight; return scrollTop + viewportHeight >= maxScrollHeight; }; const checkNode = ($ele: HTMLElement) => { // 已滚动的距离 const scrollTop: number = $ele.scrollTop; // 视窗高度 const viewportHeight: number = $ele.clientHeight; // 最大滚动距离 const maxScrollHeight: number = $ele.scrollHeight - viewportHeight - nearBottomHeight; return scrollTop >= maxScrollHeight; }; const firstArg = args[0]; if (args.length === 0 || typeof args[0] === "number") { // nearBottomHeight // return checkWindow(); } else if (typeof args[0] === "object" && args[0] instanceof HTMLElement) { // target // target,nearBottomHeight if (typeof args[1] === "number" && !Number.isNaN(args[1])) { nearBottomHeight = args[1]; } return checkNode(args[0]); } else { throw new TypeError(`参数1类型错误${typeof firstArg}`); } } /** * 判断对象是否是元素 * @param target * @returns * + true 是元素 * + false 不是元素 * @example * Utils.isDOM(document.querySelector("a")) * > true */ isDOM = CommonUtil.isDOM.bind(CommonUtil); /** * 判断浏览器是否支持全屏 */ isFullscreenEnabled(): boolean; isFullscreenEnabled(): boolean { return !!( (this.windowApi.document as any).fullscreenEnabled || (this.windowApi.document as any).webkitFullScreenEnabled || (this.windowApi.document as any).mozFullScreenEnabled || (this.windowApi.document as any).msFullScreenEnabled ); } /** * 判断对象是否是jQuery对象 * @param target * @returns * + true 是jQuery对象 * + false 不是jQuery对象 * @example * Utils.isJQuery($("a")) * > true */ isJQuery(target: any): boolean; isJQuery(target: any): boolean { let result = false; if (typeof jQuery === "object" && target instanceof jQuery) { result = true; } if (target == null) { return false; } if (typeof target === "object") { /* 也有种可能,这个jQuery对象是1.8.3版本的,页面中的jQuery是3.4.1版本的 */ const jQueryProps = [ "add", "addBack", "addClass", "after", "ajaxComplete", "ajaxError", "ajaxSend", "ajaxStart", "ajaxStop", "ajaxSuccess", "animate", "append", "appendTo", "attr", "before", "bind", "blur", "change", "children", "clearQueue", "click", "clone", "closest", "constructor", "contents", "contextmenu", "css", "data", "dblclick", "delay", "delegate", "dequeue", "each", "empty", "end", "eq", "extend", "fadeIn", "fadeOut", "fadeTo", "fadeToggle", "filter", "find", "first", "focus", "focusin", "focusout", "get", "has", "hasClass", "height", "hide", "hover", "html", "index", "init", "innerHeight", "innerWidth", "insertAfter", "insertBefore", "is", "jquery", "keydown", "keypress", "keyup", "last", "load", "map", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup", "next", "nextAll", "not", "off", "offset", "offsetParent", "on", "one", "outerHeight", "outerWidth", "parent", "parents", "position", "prepend", "prependTo", "prev", "prevAll", "prevUntil", "promise", "prop", "pushStack", "queue", "ready", "remove", "removeAttr", "removeClass", "removeData", "removeProp", "replaceAll", "replaceWith", "resize", "scroll", "scrollLeft", "scrollTop", "select", "show", "siblings", "slice", "slideDown", "slideToggle", "slideUp", "sort", "splice", "text", "toArray", "toggle", "toggleClass", "trigger", "triggerHandler", "unbind", "width", "wrap", ]; for (const jQueryPropsName of jQueryProps) { if (!(jQueryPropsName in target)) { result = false; /* console.log(jQueryPropsName); */ break; } else { result = true; } } } return result; } /** * 判断当前设备是否是移动端 * @param userAgent (可选)UA字符串,默认使用当前的navigator.userAgent * @returns * + true 是移动端 * + false 不是移动端 * @example * Utils.isPhone(); * > true **/ isPhone(userAgent?: string): boolean; isPhone(userAgent: string = navigator.userAgent): boolean { return Boolean(/(iPhone|iPad|iPod|iOS|Android|Mobile)/i.test(userAgent)); } /** * 判断传递的字符串是否是由相同的字符组成 * @param targetStr 需要判断的字符串,长度(.length)需要≥2 * @param coefficient 系数(默认:1),某个字符重复的系数大于它那么就是返回true,默认全部 */ isSameChars(targetStr: string, coefficient?: number): boolean; isSameChars(targetStr: string, coefficient: number = 1): boolean { if (typeof targetStr !== "string") { throw new TypeError("参数 str 必须是 string 类型"); } if (targetStr.length < 2) { return false; } targetStr = targetStr.toLowerCase(); const targetCharMap: UtilsOwnObject<string> = {}; let targetStrLength = 0; for (const char of targetStr) { if (Reflect.has(targetCharMap, char)) { (targetCharMap as any)[char]++; } else { (targetCharMap as any)[char] = 1; } targetStrLength++; } let result = false; for (const char in targetCharMap) { if ((targetCharMap as any)[char] / targetStrLength >= coefficient) {