import doubletap from "@any-touch/doubletap"; import AnyTouch from "any-touch"; import { EventEmiter } from "../event/EventEmiter"; import { PopsCore } from "../PopsCore"; import type { PopsUtilsOwnObject } from "../types/main"; class PopsUtils { /** * 超时时间 */ sleep(timeout: number) { return new Promise((resolve) => { setTimeout(() => { resolve(true); }, timeout); }); } /** * 判断是否是window,例如window、self、globalThis * @param target */ isWin(target: any) { if (typeof target !== "object") { return false; } if (target instanceof Node) { return false; } if (target === globalThis) { return true; } if (target === window) { return true; } if (target === self) { return true; } if (target === PopsCore.globalThis) { return true; } if (target === PopsCore.window) { return true; } if (target === PopsCore.self) { return true; } if (typeof unsafeWindow !== "undefined" && target === unsafeWindow) { return true; } if (target?.Math?.toString() !== "[object Math]") { return false; } return true; } /** * 判断对象是否是元素 * @param target * @returns * + true 是元素 * + false 不是元素 * @example * Utils.isDOM(document.querySelector("a")) * > true */ isDOM(target: any): boolean; isDOM(target: any): boolean { return target instanceof Node; } /** * 判断是否是元素列表 * @param $ele */ isNodeList($ele: any): $ele is any[] | NodeList { return Array.isArray($ele) || $ele instanceof NodeList; } /** * 删除对象上的属性 * @param target * @param propName */ delete(target: any, propName: any) { if (typeof Reflect === "object" && typeof Reflect.deleteProperty === "function") { Reflect.deleteProperty(target, propName); } else { delete target[propName]; } } /** * 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(target: T1, source: T2, isAdd?: T3): T3 extends true ? T1 & T2 : T1; assign(target = {}, source = {}, isAdd = false) { const UtilsContext = this; if (Array.isArray(source)) { const canTraverse = source.filter((item) => { return typeof item === "object"; }); if (!canTraverse.length) { return source; } } if (source == null) { return target; } if (target == null) { target = {}; } // 当前遍历的目标对象 let iteratorTarget; if (isAdd) { // 追加并覆盖是以source为准 iteratorTarget = source; } else { // 覆盖以target为准 iteratorTarget = target; } for (const keyName in iteratorTarget) { if (!isAdd && !(keyName in source)) { // 仅替换 但是源端没有此键 continue; } const targetValue = Reflect.get(target, keyName); const sourceValue = Reflect.get(source, keyName); if ( typeof sourceValue === "object" && sourceValue != null && keyName in target && !UtilsContext.isDOM(sourceValue) && !(sourceValue instanceof EventEmiter) ) { // 源端的值是object类型,且不是元素节点 // 如果是数组,那此数组中有值,清空旧的数组再赋值 let childObjectValue; if (Array.isArray(sourceValue)) { if (Array.isArray(targetValue)) { targetValue.length = 0; } childObjectValue = sourceValue; } else { childObjectValue = UtilsContext.assign(targetValue, sourceValue, isAdd); } Reflect.set(target, keyName, childObjectValue); } else { // 直接赋值 Reflect.set(target, keyName, sourceValue); } } return target; } /** * 生成uuid */ getRandomGUID() { if (typeof PopsCore.globalThis?.crypto?.randomUUID === "function") { return PopsCore.globalThis.crypto.randomUUID(); } else { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (charStr) { const randomValue = (Math.random() * 16) | 0, randomCharValue = charStr === "x" ? randomValue : (randomValue & 0x3) | 0x8; return randomCharValue.toString(16); }); } } /** * 判断元素/页面中是否包含该元素 * @param target 需要判断的元素 * @param context 默认为body */ contains(target: any): boolean; contains(context: any, target?: any): boolean; contains(...args: any[]): boolean { const [context, target] = args; if (args.length === 1) { // 只判断该页面是否存在该元素 return this.contains(PopsCore.document.body || PopsCore.document.documentElement, args[0]); } else { if (target == null) { return false; } if (typeof target[Symbol.iterator] === "function") { // 可遍历的数组 let flag = true; for (const targetNode of target as any) { if (!context.contains(targetNode)) { flag = false; break; } } return flag; } else { return context.contains(target); } } } /** * 获取格式化后的时间 * @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" ): 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; } /** * 格式化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, addType?: T): T extends true ? string : number; formatByteToSize(byteSize: number | string, addType = true) { byteSize = parseInt(byteSize.toString()); if (isNaN(byteSize)) { throw new TypeError("Utils.formatByteToSize 参数 byteSize 格式不正确"); } let result = 0; let resultType = "KB"; const sizeData: PopsUtilsOwnObject = {}; 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; } } new Date(); result = result.toFixed(2) as any; result = addType ? result + resultType.toString() : (parseFloat(result.toString()) as any); return result; } /** * https://github.com/any86/any-touch/blob/master/README.CN.md */ AnyTouch = () => { return AnyTouch; }; /** * `any-touch`的`doubletap`事件插件 */ AnyTouchDoubleTapPlugin = () => { return doubletap; }; /** * 通过navigator.userAgent判断是否是手机访问 * @param userAgent */ isPhone(userAgent = PopsCore.globalThis.navigator.userAgent): boolean { return Boolean(/(iPhone|iPad|iPod|iOS|Android)/i.test(userAgent)); } /** * 自动使用 Worker 执行 setTimeout */ setTimeout(callback: (...args: any[]) => any, timeout: number = 0) { return PopsCore.setTimeout(callback, timeout); } /** * 配合 .setTimeout 使用 */ clearTimeout(timeId: number | undefined) { return PopsCore.clearTimeout(timeId); } /** * 自动使用 Worker 执行 setInterval */ setInterval(callback: (...args: any[]) => any, timeout: number = 0) { return PopsCore.setInterval(callback, timeout); } /** * 配合 .setInterval 使用 */ clearInterval(timeId: number | undefined) { return PopsCore.clearInterval(timeId); } /** * 覆盖对象中的数组新值 */ setArray(target: T, key: keyof T, newArr: any[]) { if (target == null) return; if (!Array.isArray(newArr)) return; const arr: any = target[key]; if (Array.isArray(target[key])) { arr.length = 0; } else { (target)[key] = []; } (target)[key] = newArr; } /** * 获取页面的坐标中最大的z-index的元素信息 * * 矩阵坐标计算 * @param $el 仅检测目标元素最大的z-index(自动往上层找) * @param deviation 将对所有获取到的z-index处理偏移量(增加或减少),默认为10 * @example * Utils.getMaxZIndexNodeInfoFromPoint(document.querySelector("a")); * @example * Utils.getMaxZIndexNodeInfoFromPoint(document.querySelector("a"), 20); * @example * Utils.getMaxZIndexNodeInfoFromPoint([document.querySelector("a"), document.querySelector("div")]); * @example * Utils.getMaxZIndexNodeInfoFromPoint({x: 500, y: 500}); * @example * Utils.getMaxZIndexNodeInfoFromPoint({x: 500, y: 500}, 20); * @example * Utils.getMaxZIndexNodeInfoFromPoint(() => {x: 500, y: 500}, 20); */ getMaxZIndexNodeInfoFromPoint( $el?: IFunction | IArray<{ x: number; y: number }>>, deviation?: number ): { /** 处理了偏移量和阈值比较后的z-index值 */ zIndex: number; /** 原始z-index值 */ originZIndex: number; /** 拥有最大z-index的元素 */ node: HTMLElement | null; /** 目标坐标元素 */ positionNode: HTMLElement; /** x坐标 */ positionX: number; /** y坐标 */ positionY: number; }[]; /** * 获取页面的坐标中最大的z-index的元素信息 * * 矩阵坐标计算 * @param deviation 将对所有获取到的z-index处理偏移量(增加或减少) * @example * Utils.getMaxZIndexNodeInfoFromPoint(20); */ getMaxZIndexNodeInfoFromPoint(deviation: IFunction): { /** 处理了偏移量和阈值比较后的z-index值 */ zIndex: number; /** 原始z-index值 */ originZIndex: number; /** 拥有最大z-index的元素 */ node: HTMLElement | null; /** 目标坐标元素 */ positionNode: HTMLElement; /** x坐标 */ positionX: number; /** y坐标 */ positionY: number; }[]; getMaxZIndexNodeInfoFromPoint( $el?: IFunction | number | IArray<{ x: number; y: number }>>, deviation?: number ): { /** 处理了偏移量和阈值比较后的z-index值 */ zIndex: number; /** 原始z-index值 */ originZIndex: number; /** 拥有最大z-index的元素 */ node: HTMLElement | null; /** 目标坐标元素 */ positionNode: HTMLElement; /** x坐标 */ positionX: number; /** y坐标 */ positionY: number; }[] { if (typeof $el === "function") { $el = $el(); } if (typeof $el === "number") { deviation = $el; $el = void 0; } if (typeof deviation !== "number" || Number.isNaN(deviation)) { deviation = 10; } // 最大值 2147483647 // const maxZIndex = Math.pow(2, 31) - 1; // 比较值 2000000000 const maxZIndexCompare = 2 * Math.pow(10, 9); /** 坐标偏移 */ const positionDistance = 10; const defaultCalcPostion: { x: number; y: number; }[] = []; const maxPostionX = globalThis.innerWidth; const maxPostionY = globalThis.innerHeight; const gridXCount = 8; const gridYCount = 8; for (let i = 0; i < gridXCount; i++) { for (let j = 0; j < gridYCount; j++) { const positionX = globalThis.innerWidth * (i / gridXCount) + positionDistance; const positionY = globalThis.innerHeight * (j / gridYCount) + positionDistance; const position: (typeof defaultCalcPostion)[0] = { x: positionX, y: positionY, }; if (position.x > maxPostionX) { position.x = maxPostionX; } if (position.y > maxPostionY) { position.y = maxPostionY; } defaultCalcPostion.push(position); } } const delayHandlerElementPostionList: ({ x: number; y: number } | HTMLElement)[] = defaultCalcPostion; if ($el) { delayHandlerElementPostionList.length = 0; if (Array.isArray($el)) { delayHandlerElementPostionList.push(...$el); } else { delayHandlerElementPostionList.push($el); } } const positionInfoList = delayHandlerElementPostionList .map((position) => { let $position: Element | null; let positionX: number; let positionY: number; if (position instanceof HTMLElement) { $position = position; const nodeRect = position.getBoundingClientRect(); positionX = nodeRect.x + nodeRect.width / 2; positionY = nodeRect.y + nodeRect.height / 2; } else { $position = document.elementFromPoint(position.x, position.y); positionX = position.x; positionY = position.y; } const shadowRoot = $position?.shadowRoot; if (shadowRoot) { // 处理ShadowRoot $position = shadowRoot.elementFromPoint(positionX, positionY); } if (!($position instanceof HTMLElement)) return; let $parent: HTMLElement | null = $position; let zIndex = 0; let $maxZIndexNode: HTMLElement | null = null; let rect = { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0, } as Omit; while ($parent) { const nodeStyle = globalThis.getComputedStyle($parent); const nodeZIndex = parseInt(nodeStyle.zIndex); if (nodeStyle.position !== "static" && !isNaN(nodeZIndex)) { if (nodeZIndex > zIndex) { zIndex = nodeZIndex; $maxZIndexNode = $parent; } } $parent = $parent.parentElement; } if ($maxZIndexNode) { const maxRect = $maxZIndexNode.getBoundingClientRect(); rect = { x: maxRect.x, y: maxRect.y, width: maxRect.width, height: maxRect.height, top: maxRect.top, right: maxRect.right, bottom: maxRect.bottom, left: maxRect.left, }; } const calcZIndex = zIndex + deviation; if (calcZIndex >= maxZIndexCompare) { // 不要超过最大值 return; } return { /** 计算偏移量后的z-index值 */ zIndex: calcZIndex, /** 获取到的最大的z-index值 */ originZIndex: zIndex, /** 拥有最大z-index的元素 */ node: $maxZIndexNode, /** 目标坐标元素 */ positionNode: $position, /** 目标x坐标 */ positionX: positionX, /** 目标y坐标 */ positionY: positionY, /** node位置信息 */ rect, }; }) .filter((it) => it != null); // 降序排序 positionInfoList.sort((a, b) => { if (a.zIndex < b.zIndex) { return 1; } else if (a.zIndex > b.zIndex) { return -1; } else { return 0; } }); return positionInfoList; } } const popsUtils = new PopsUtils(); export { popsUtils };