UNPKG

@mt-kit/utils

Version:
1,615 lines (1,496 loc) 117 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["microUtil"] = factory(); else root["microUtil"] = factory(); })(self, function() { return /******/ (function() { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ([ /* 0 */, /* 1 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ animationFrameThrottle; } /* harmony export */ }); /** * 创建一个使用 requestAnimationFrame 的函数节流(throttle)版本 * * 函数节流的目的是限制一个函数在特定时间内的调用次数,以避免过于频繁的执行,以确保性能优化或更平滑的动画效果时 * * @param fn * @returns * * 使用场景: * * 1、滚动事件处理 * window.addEventListener('scroll', animationFrameThrottle(handleScroll)); * * 2、窗口大小改变 * window.addEventListener('resize', animationFrameThrottle(handleResize)); * * 3、动画效果 * const animatedFunction = animationFrameThrottle(updateAnimation); */ function animationFrameThrottle(fn) { let locked = false; return function (...args) { if (locked) { return undefined; } locked = true; // window.requestAnimationFrame 用于在下一次浏览器重绘之前调用指定的函数 window.requestAnimationFrame(() => { fn.apply(this, args); locked = false; }); return undefined; }; } /***/ }), /* 2 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ imageBase64ToBlob; } /* harmony export */ }); /** * 将 base64 编码的图像数据转换为 Blob 对象 * @param base64Buf base64Buf 是包含 base64 编码的图像数据的字符串 * @returns 返回一个 Blob 对象,该对象表示解码后的图像数据 */ function imageBase64ToBlob(base64Buf) { const arr = base64Buf.split(","); const [typeItem] = arr; const [, mime] = typeItem.match(/:(.*?);/) || [ "", "" ]; const bstr = window.atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.codePointAt(n) || 0; } return new Blob([ u8arr ], { type: mime }); } /***/ }), /* 3 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ imageUrlToBase64; } /* harmony export */ }); /** * 将图像 URL 转换为 base64 编码的字符串 * @param url 是要转换的图像的 URL * @param mineType 是可选参数,用于指定生成的 base64 字符串的 MIME 类型,默认为 image/png * @returns 返回一个 Promise 对象,resolve 后的值是生成的 base64 编码的字符串 */ function imageUrlToBase64(url, mineType) { return new Promise((resolve, reject) => { let canvas = document.createElement("CANVAS"); const ctx = canvas === null || canvas === void 0 ? void 0 : canvas.getContext("2d"); const img = new Image(); img.crossOrigin = ""; img.addEventListener("load", () => { if (!canvas || !ctx) { return reject(new Error("Canvas or context is not available")); } canvas.height = img.height; canvas.width = img.width; ctx.drawImage(img, 0, 0); const dataUrl = canvas.toDataURL(mineType || "image/png"); canvas = null; resolve(dataUrl); }); img.src = url; }); } /***/ }), /* 4 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ downloadByUrl; } /* harmony export */ }); /* harmony import */ var _open_window__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5); /** * 根据文件地址进行下载 * * Url 文件的地址 * * target 链接的打开方式,默认为 '_blank' * * fileName 保存的文件名,如果不提供会从 URL 中提取 * * isCORS 是否是 CORS 跨域请求,默认为 false */ function downloadByUrl({ url, target = "_blank", fileName, isCORS = false }) { const isChrome = window.navigator.userAgent.toLowerCase().includes("chrome"); const isSafari = window.navigator.userAgent.toLowerCase().includes("safari"); if ((/iP/).test(window.navigator.userAgent)) { console.error("Your browser does not support download!"); return false; } if (isChrome || isSafari) { const link = document.createElement("a"); // 处理 CORS 跨域问题 if (isCORS) { // 强制添加 response-content-disposition=attachment 参数下载文件 link.href = url.includes("?") ? `${url}&response-content-disposition=attachment` : `${url}?response-content-disposition=attachment`; } else { link.href = url; } link.target = target; if (link.download !== undefined) { link.download = fileName || url.slice(url.lastIndexOf("/") + 1); } if (document.createEvent) { const e = document.createEvent("MouseEvents"); e.initEvent("click", true, true); link.dispatchEvent(e); return true; } } if (!url.includes("?")) { url += "?download"; } (0,_open_window__WEBPACK_IMPORTED_MODULE_0__["default"])(url, { target }); return true; } /***/ }), /* 5 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ openWindow; } /* harmony export */ }); /** * 用于在浏览器中打开窗口 * @param {string} url 要打开的目标 URL * @param {IOptions} opt */ function openWindow(url, opt) { const { target = "__blank", noopener = true, noreferrer = true } = opt || {}; const feature = []; noopener && feature.push("noopener=yes"); noreferrer && feature.push("noreferrer=yes"); window.open(url, target, feature.join(",")); } /***/ }), /* 6 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ downloadDataFile; } /* harmony export */ }); /** * 根据文件数据进行下载 * @param {*} data Blob 对象的 BlobPart 参数 * @param {*} filename 保存的文件名 * @param {*} mime 文件的 MIME 类型 * @param {*} bom Blob 对象的 BlobPart 参数 */ function downloadDataFile(data, filename, mime, bom) { const blobData = bom === undefined ? [ data ] : [ bom, data ]; const blob = new Blob(blobData, { type: mime || "application/octet-stream" }); const blobURL = window.URL.createObjectURL(blob); const tempLink = document.createElement("a"); tempLink.style.display = "none"; tempLink.href = blobURL; tempLink.setAttribute("download", filename); if (tempLink.download === undefined) { tempLink.setAttribute("target", "_blank"); } document.body.append(tempLink); tempLink.click(); tempLink.remove(); window.URL.revokeObjectURL(blobURL); } /***/ }), /* 7 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ downloadBase64File; } /* harmony export */ }); /* harmony import */ var _download_data_file__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _image_base64_to_blob__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); /** * 根据 Base64 编码的字符串进行下载 * @param buf Base64 编码的字符串 * @param filename 保存的文件名 * @param mime 文件的 MIME 类型 * @param bom Blob 对象的 BlobPart 参数 */ function downloadBase64File(buf, filename, mime, bom) { const base64Buf = (0,_image_base64_to_blob__WEBPACK_IMPORTED_MODULE_1__["default"])(buf); (0,_download_data_file__WEBPACK_IMPORTED_MODULE_0__["default"])(base64Buf, filename, mime, bom); } /***/ }), /* 8 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ downloadUrlFile; } /* harmony export */ }); /* harmony import */ var _download_base64_file__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7); /* harmony import */ var _image_url_to_base64__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3); /** * 根据在线图片的 URL 进行下载 * @param url 在线图片的 URL * @param filename 保存的文件名 * @param mime 文件的 MIME 类型 * @param bom Blob 对象的 BlobPart 参数 */ function downloadUrlFile(url, filename, mime, bom) { (0,_image_url_to_base64__WEBPACK_IMPORTED_MODULE_1__["default"])(url).then(base64 => { (0,_download_base64_file__WEBPACK_IMPORTED_MODULE_0__["default"])(base64, filename, mime, bom); }); } /***/ }), /* 9 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ copyText; } /* harmony export */ }); /** * 文本复制 * * `navigator.clipboard` 可能因浏览器设置或浏览器兼容而造成兼容问题 * * @param {string} text 要复制的文字 * @param {Function} promptFn 复制成功后的回调 */ function copyText(text, promptFn) { if (navigator.clipboard) { return navigator.clipboard. writeText(text). then(() => { promptFn && promptFn(); }). catch(error => { console.error(error); return error; }); } if (Reflect.has(document, "execCommand")) { return new Promise((resolve, reject) => { const textArea = document.createElement("textarea"); textArea.value = text; // 优化隐藏逻辑 textArea.style.opacity = "0"; textArea.style.position = "fixed"; textArea.style.top = "0"; textArea.style.left = "-9999px"; textArea.setAttribute("readonly", "readonly"); document.body.append(textArea); try { textArea.focus(); // 兼容移动端聚焦问题 textArea.select(); const success = document.execCommand("copy"); if (!success) { throw new Error("execCommand('copy') 执行失败"); } promptFn === null || promptFn === void 0 ? void 0 : promptFn(); } catch (error) { console.error(error); reject(error); } finally { textArea.remove(); // 确保清理 } }); } return Promise.reject(new Error("\"navigator.clipboard\" 或 \"document.execCommand\" 中存在API错误, 拷贝失败!")); } /***/ }), /* 10 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ queryStringToObject; } /* harmony export */ }); /** * window.location.search 的值转换为 Object * @param queryString window.location.search * @returns {Record<string, string>} Object */ function queryStringToObject(queryString) { const params = new URLSearchParams(queryString); const result = {}; params.forEach((value, key) => { result[key] = value; }); return result; } /***/ }), /* 11 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /** * 封装 Cookie 操作的工具类 * * 端口、协议不同,不影响 Cookie * * */ const cookieHelper = { /** * 设置 Cookie * @param {string} name - Cookie 的名称 * @param {string} value - Cookie 的值 * @param {CookieOptions} options - Cookie 的选项 */ setCookie(name, value, options) { let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`; if (options === null || options === void 0 ? void 0 : options.expires) { const expirationDate = new Date(Date.now() + options.expires * 1000); cookieString += `; expires=${expirationDate.toUTCString()}`; } if (options === null || options === void 0 ? void 0 : options.domain) { cookieString += `; domain=${options.domain}`; } if (options === null || options === void 0 ? void 0 : options.path) { cookieString += `; path=${options.path}`; } if (options === null || options === void 0 ? void 0 : options.secure) { cookieString += "; secure"; } if (options === null || options === void 0 ? void 0 : options.sameSite) { cookieString += `; samesite=${options.sameSite}`; } // eslint-disable-next-line unicorn/no-document-cookie document.cookie = cookieString; }, /** * 获取 Cookie 的值 * @param {string} name - Cookie 的名称 * @returns {string|null} Cookie 的值,如果不存在则返回 null */ getCookie(name) { const cookies = document.cookie.split("; "); for (const cookie of cookies) { const [cookieName, cookieValue] = cookie.split("="); if (decodeURIComponent(cookieName) === name) { return decodeURIComponent(cookieValue); } } return null; }, /** * 删除 Cookie * @param {string} name - Cookie 的名称 * @param {CookieOptions} options - Cookie 的路径(一版不需要) */ deleteCookie(name, options) { if (!this.getCookie(name)) { return; } // 设置过期时间为过去的时间来删除 Cookie const deleteOptions = { domain: options === null || options === void 0 ? void 0 : options.domain, expires: -1, path: options === null || options === void 0 ? void 0 : options.path, sameSite: options === null || options === void 0 ? void 0 : options.sameSite, secure: options === null || options === void 0 ? void 0 : options.secure }; this.setCookie(name, "", deleteOptions); } }; /* harmony default export */ __webpack_exports__["default"] = (cookieHelper); /* * // 使用示例 * const cookieOptions: CookieOptions = { * expires: 3600, * domain: 'example.com', * path: '/', * secure: true, * sameSite: 'None' * }; * * CookieHelper.setCookie('username', 'John Doe', cookieOptions); * const username = CookieHelper.getCookie('username'); * console.log(username); * * CookieHelper.deleteCookie('username', cookieOptions); */ /***/ }), /* 12 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /** * localStorage 的一些扩展 */ const localStorageHelper = { // private static readonly storageKeyPrefix = 'test'; /** * 设置 localStorage */ set({ key, value, expire }) { if (key === "" || key === null || key === undefined) { throw new Error("key 不能为空"); } if (value === "" || value === null || value === undefined) { throw new Error("value 不能为空"); } localStorage.setItem(key, JSON.stringify({ value, expire: expire ? Date.now() + expire * 24 * 60 * 60 * 1000 : null })); }, get(key) { const storedItem = localStorage.getItem(key); if (storedItem) { const parsedItem = JSON.parse(storedItem); if (parsedItem === null || parsedItem === void 0 ? void 0 : parsedItem.expire) { const now = Date.now(); if (parsedItem.expire === Infinity || parsedItem.expire > now) { return parsedItem.value; } localStorage.removeItem(key); return null; } delete parsedItem.expire; return parsedItem.value; } return null; }, delete(key) { localStorage.removeItem(key); }, clear() { localStorage.clear(); } }; /* harmony default export */ __webpack_exports__["default"] = (localStorageHelper); /***/ }), /* 13 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ isElement; } /* harmony export */ }); /** * 是否为 DOM * @param element 元素 * @return {element is Element} */ function isElement(element) { return (element instanceof Element || element !== undefined && element !== null && typeof element === "object" && "nodeType" in element && typeof element.nodeType === "number" && "nodeName" in element && typeof element.nodeName === "string"); } /***/ }), /* 14 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ isEqual; } /* harmony export */ }); /** * 比较对象是否相等 * @param obj1 * @param obj2 * @returns boolean */ function isEqual(obj1, obj2, visited = new Set()) { if (obj1 === obj2) { return true; } if (typeof obj1 !== "object" || obj1 === null || typeof obj2 !== "object" || obj2 === null) { return false; } if (visited.has(obj1) || visited.has(obj2)) { return true; } visited.add(obj1); visited.add(obj2); const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) { visited.clear(); return false; } for (const key of keys1) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (!keys2.includes(key) || !isEqual(obj1[key], obj2[key], visited)) { visited.clear(); return false; } } visited.clear(); // 新增日期比较和数组顺序比较 if (obj1 instanceof Date && obj2 instanceof Date) { return obj1.getTime() === obj2.getTime(); } if (Array.isArray(obj1) && Array.isArray(obj2)) { if (obj1.length !== obj2.length) { return false; } for (const [i, element] of obj1.entries()) { if (!isEqual(element, obj2[i], visited)) { return false; } } return true; } return true; } // // 测试 // const objA = { a: 1, b: { c: 2 } }; // const objB = { a: 1, b: { c: 2 } }; // const objC = { a: 1, b: { c: 3 } }; // console.log(isEqual(objA, objB)); // true // console.log(isEqual(objA, objC)); // false // // 循环引用测试 // const objD = { a: 1, b: { c: 2 } }; // objD.d = objD; // const objE = { a: 1, b: { c: 2 } }; // objE.b.d = objE; // console.log(isEqual(objD, objE)); // true /***/ }), /* 15 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ isFunction; } /* harmony export */ }); /* eslint-disable @typescript-eslint/no-explicit-any */ /** * 是否函数类型判断 * @param value * @return {value is Function} 如果 value 为 function,则返回 true,否则返回 false */ function isFunction(value) { return typeof value === "function"; } /***/ }), /* 16 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* eslint-disable @typescript-eslint/no-explicit-any */ /** * 判断是否为 null * @param value * @returns boolean */ function isNull(value) { return value == null; } /* harmony default export */ __webpack_exports__["default"] = (isNull); /***/ }), /* 17 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ isObject; } /* harmony export */ }); /** * 是否对象类型判断 * * value is Record<string, any> * 函数的返回类型是 value is Record<string, any>,这种形式的返回类型是 TypeScript 中用于表达类型守卫的一种方式。这意味着如果函数返回 true,TypeScript 将会把传入的参数视为 Record<string, any> 类型;如果返回 false,则参数不会被视为这个类型。 * * @return 如果 value 为 object,则返回 true,否则返回 false */ function isObject(value) { const type = typeof value; return value !== null && (type === "object" || type === "function"); } /***/ }), /* 18 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ isUndefined; } /* harmony export */ }); /** * 是否为 undefined * @param value value要检查的值 * @return {boolean} 如果 value 为 undefined,则返回 true,否则返回 false */ function isUndefined(value) { return value === undefined; } /***/ }), /* 19 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ clone; } /* harmony export */ }); /** * 浅拷贝 */ function clone(value) { /** * 或: * return Object.assign({}, value); */ return { ...value }; } /***/ }), /* 20 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ cloneDeep; } /* harmony export */ }); /* harmony import */ var _is_function__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(15); /* harmony import */ var _is_object__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(17); /* eslint-disable guard-for-in */ /* eslint-disable @typescript-eslint/no-empty-object-type */ /** * 深拷贝 * * @param value * @param map 外部使用的时候,不需要传入 * @return value * * 对所有类型都进行了拷贝 * * lodashjs、underscorejs 对函数、Symbol、Set、Map 等没做深拷贝处理 * * 不建议使用:JSON.parse(JSON.stringify(value)); 它存在以下缺陷(案例见 ../stories/demo-clone-deep.html): * ① 无法解析 Symbol 作为 key 或者 value * ② 无法循环引用 */ function cloneDeep(value, map = new WeakMap()) { try { // Set 类型 if (value instanceof Set) { if (map.has(value)) { return map.get(value); } const newSet = new Set(); map.set(value, newSet); value.forEach(item => { newSet.add(cloneDeep(item, map)); }); return newSet; } // Map 类型 if (value instanceof Map) { if (map.has(value)) { return map.get(value); } const newMap = new Map(); map.set(value, newMap); value.forEach((val, key) => { newMap.set(cloneDeep(key, map), cloneDeep(val, map)); }); return newMap; } // Symbol 类型 if (typeof value === "symbol") { return Symbol(value.description); } // 函数类型 if ((0,_is_function__WEBPACK_IMPORTED_MODULE_0__["default"])(value)) { return function (...args) { return value.apply(this, args); }; } // 对象类型 if (!(0,_is_object__WEBPACK_IMPORTED_MODULE_1__["default"])(value)) { return value; } // 判断 map 中是否有值,有了直接 return if (map.has(value)) { return map.get(value); } // 判断传入的对象是数组, 还是对象 const newValue = Array.isArray(value) ? [] : {}; // Map 没有值 map.set(value, newValue); for (const key in value) { newValue[key] = cloneDeep(value[key], map); } // Symbol 作为 key 的处理(这个可以不做 Symbol(sKey.description) 处理) const symbolKeys = Object.getOwnPropertySymbols(value); for (const sKey of symbolKeys) { const newSKey = Symbol(sKey.description); Object.defineProperty(newValue, newSKey, { enumerable: true, configurable: true, writable: true, value: cloneDeep(Reflect.get(value, sKey), map) }); } return newValue; } catch { throw new Error("Deep clone handle error!"); } } /***/ }), /* 21 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ debounce; } /* harmony export */ }); /** * 防抖 * * 只有在某个时间内,没有再次触发某个函数时,才真正的调用这个函数 * * 防抖的应用场景: * ① 搜索框(确保不会每次输入都发送请求); * ② 频繁的点击按钮,触发某个事件; * ③ 监听浏览器滚动事件,完成某些特定操作; * ④ 用户缩放浏览器的 resize 事件。 * * @param {Function} func 执行的方法 * @param {number} wait 毫秒 * @param {boolean} immediate 立即执行(让其在第一次输入的时候,就给用户一个联想) * @returns 返回一个函数,该函数返回一个 Promise,解析为执行的方法的返回值。另外,该函数还具有一个 cancel 方法,用于取消防抖 */ function debounce(func, wait = 250, immediate = false) { /** * setTimeout 的返回类型是 number | NodeJS.Timeout。这是因为在 Node.js 环境中,setTimeout 返回的是一个 Timeout 对象,而在浏览器环境中,返回的是一个数字。 */ let timer = null; /** * 控制在中间暂停的时候,接下来也能立即执行 */ let isInvoke = false; const _debounce = function (...args) { /** * 在外部获取 fn 中的返回值 */ return new Promise((resolve, reject) => { try { if (timer) { clearTimeout(timer); } if (immediate && !isInvoke) { const result = func.apply(this, args); isInvoke = true; resolve(result); return; } timer = setTimeout(() => { /** * 确保 this 指向的正确 */ const result = func.apply(this, args); isInvoke = false; resolve(result); }, wait); } catch (error) { reject(error); } }); }; /** * 取消功能:在触发的过程中,突然要取消 * 取消之后,重置所有变量 */ _debounce.cancel = function () { if (timer) { clearTimeout(timer); timer = null; isInvoke = false; } }; return _debounce; } /* // 简易版 let timer = null; function debounce() { if(timer!=null){ window.clearTimeout(myTimer); } //重新启动定时器 timer = setTimeout(()=>{ console.log("我是防抖"); timer =null; },200); } // ts 类型定义解释: // // 通过泛型 T,你明确定义了 func 参数的类型,即一个函数,它接受任意参数并返回任意类型。 // // 对于返回的函数,你使用了 Parameters<T> 获取 func 参数的类型,确保返回的函数接受与原始函数相同的参数。 // // 使用 Promise<ReturnType<T>> 表示返回的函数返回一个 Promise,该 Promise 解析为原始函数的返回类型。 // // 你使用了一个交叉类型 &,将返回的函数与具有 cancel 方法的对象类型组合在一起。这使得你的函数在使用时既能调用返回的函数,又能调用 cancel 方法。 */ /***/ }), /* 22 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ throttle; } /* harmony export */ }); /** * 节流 * * ① 当事件触发时,会执行这个事件的响应函数,如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数 * ② 不管在这个中间有多少次触发这个事件,执行函数的频率总是固定的,不管在中间究竟点了几次 * ③ 他在初次是立即触发的 * * 节流的应用场景: * ① 监听页面的滚动事件; * ② 鼠标移动事件; * ③ 用户频繁点击按钮操作; * ④ 按照固定的频率去触发时。 * * @param {Function} func 执行的方法 * @param {number} wait 毫秒 * @param {IOptions} options * @returns 返回一个函数,该函数返回一个 Promise,解析为执行的方法的返回值。另外,该函数还具有一个 cancel 方法,用于取消防抖 * * 使用: * * const inputEl = document.querySelector("input"); * * const onInput = function(event) { * console.log(event); * }; * * const onInputThrottle = throttle(onInput, 3000); * * inputEl.oninput = onInputThrottle; */ const defaultOptions = { leading: true, trailing: false }; function throttle(func, wait = 300, options = defaultOptions) { const { leading, trailing } = options; let lastTime = 0; let timer = null; /** * 事件触发时真正执行的函数 */ const _throttle = function (...args) { return new Promise((resolve, reject) => { try { /** * 获取最新的时间 * 当第一次执行完 lastTime = nowTime 时,wait - (nowTime - lastTime) 一定大于 0,这个时候是不执行的 */ const nowTime = Date.now(); const remainTime = wait - (nowTime - lastTime); if (lastTime === 0 && leading === false) { lastTime = nowTime; return; } if (remainTime <= 0) { /** * 只有在这重置了,才能开启下一个定时器 */ if (timer) { clearTimeout(timer); timer = null; } const result = func.apply(this, args); resolve(result); lastTime = nowTime; return; } if (trailing === true && remainTime > 0 && timer === null) { timer = setTimeout(() => { timer = null; const result = func.apply(this, args); /** * 处理边界性问题 */ lastTime = leading === true ? Date.now() : 0; resolve(result); }, remainTime); } } catch (error) { reject(error); } }); }; /** * 取消节流 */ _throttle.cancel = function () { if (timer) { clearTimeout(timer); timer = null; } lastTime = 0; }; return _throttle; } /* // 简易版 let timer = null; function throttle() { if( timer !== null ){ return; } timer = setTimeout(()=>{ console.log("我是节流"); timer = null; },200); } */ /***/ }), /* 23 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ queue; } /* harmony export */ }); /** * 消息队列 * 支持两种模式: * 1. 串行模式:按顺序执行,一个完成后再执行下一个 * 2. 持续防抖模式:一段时间内有新请求就取消前面的,只保留最后一个,且只有在最后一次请求结束后的一段时间内没有新请求,才真正执行 */ // 默认防抖时间(毫秒) const DEFAULT_DEBOUNCE_TIME = 300; // 存储各队列的待执行任务 const queues = new Map(); // 存储各队列正在执行的任务ID (用于取消正在执行的任务) const runningTaskIds = new Map(); // 存储各队列的取消ID集合 const cancelledIds = new Map(); // 存储队列是否正在处理中 const processing = new Map(); // 存储防抖模式下的待执行任务 (key -> item) const pendingDebounceItem = new Map(); /** * 检查任务是否被取消 */ const isCancelled = (key, id) => { var _a, _b; return (_b = (_a = cancelledIds.get(key)) === null || _a === void 0 ? void 0 : _a.has(id)) !== null && _b !== void 0 ? _b : false; }; /** * 标记之前的任务为已取消 */ const markPreviousAsCancelled = (key, currentId) => { var _a, _b; const cancelledSet = (_a = cancelledIds.get(key)) !== null && _a !== void 0 ? _a : new Set(); cancelledIds.set(key, cancelledSet); // 1. 取消排队中的任务 (普通队列模式) (_b = queues.get(key)) === null || _b === void 0 ? void 0 : _b.forEach(item => { if (item.id !== currentId) { cancelledSet.add(item.id); } }); // 2. 取消正在执行的任务 const runningId = runningTaskIds.get(key); if (runningId && runningId !== currentId) { cancelledSet.add(runningId); } // 3. 取消防抖等待中的任务 const pendingItem = pendingDebounceItem.get(key); if (pendingItem && pendingItem.id !== currentId) { cancelledSet.add(pendingItem.id); if (pendingItem.timer) { clearTimeout(pendingItem.timer); } // 立即 reject 旧任务 pendingItem.reject(new Error("任务已取消:新任务已到达")); pendingDebounceItem.delete(key); } }; /** * 清理已取消的任务ID(避免内存泄漏) */ const cleanupCancelledIds = (key) => { const cancelledSet = cancelledIds.get(key); if (!cancelledSet) { return; } // 清理已不在队列中的取消ID const queueList = queues.get(key); const runningId = runningTaskIds.get(key); const pendingItem = pendingDebounceItem.get(key); const activeIds = new Set(); if (queueList) { queueList.forEach(item => activeIds.add(item.id)); } if (runningId) { activeIds.add(runningId); } if (pendingItem) { activeIds.add(pendingItem.id); } // 只保留活跃的取消ID,清理不再需要的 if (cancelledSet.size > activeIds.size * 2) { // 如果取消集合太大,清理所有不在活跃列表中的ID const toDelete = []; cancelledSet.forEach(id => { if (!activeIds.has(id)) { toDelete.push(id); } }); toDelete.forEach(id => cancelledSet.delete(id)); } }; /** * 检查队列是否空闲,如果空闲则清理相关资源 */ const checkAndCleanQueue = (key) => { const queueList = queues.get(key); const isQueueEmpty = !queueList || queueList.length === 0; const isRunning = runningTaskIds.has(key); const isPendingDebounce = pendingDebounceItem.has(key); // 如果既没有排队的任务,也没有正在运行的任务,也没有正在防抖等待的任务,则清理资源 if (isQueueEmpty && !isRunning && !isPendingDebounce) { queues.delete(key); cancelledIds.delete(key); processing.delete(key); runningTaskIds.delete(key); pendingDebounceItem.delete(key); } else { // 队列还在使用中,清理已取消的ID避免内存泄漏 cleanupCancelledIds(key); } }; /** * 处理普通队列 */ const processQueue = async (key) => { // 如果正在处理,直接返回 if (processing.get(key)) { return; } const queueList = queues.get(key); if (!queueList || queueList.length === 0) { processing.set(key, false); runningTaskIds.delete(key); checkAndCleanQueue(key); return; } // 开始处理队列 processing.set(key, true); while (queueList.length > 0) { const item = queueList.shift(); if (!item) { break; } // 记录正在执行的任务ID runningTaskIds.set(key, item.id); // 检查是否被取消 (执行前) if (isCancelled(key, item.id)) { item.reject(new Error("任务已取消:前置任务已被取消")); continue; } try { // eslint-disable-next-line no-await-in-loop const result = await item.task(); // 检查是否被取消 (执行后) if (isCancelled(key, item.id)) { item.reject(new Error("任务已取消")); } else { item.resolve(result); } } catch (error) { // 检查是否被取消 (出错后) if (!isCancelled(key, item.id)) { item.reject(error); } // 如果被取消,静默忽略错误 } } // 处理完成,清理状态 runningTaskIds.delete(key); processing.set(key, false); checkAndCleanQueue(key); }; /** * 执行单个任务 (用于防抖模式) */ const executeTask = async (key, item) => { var _a; runningTaskIds.set(key, item.id); if (isCancelled(key, item.id)) { item.reject(new Error("任务已取消")); runningTaskIds.delete(key); checkAndCleanQueue(key); return; } try { const result = await item.task(); if (isCancelled(key, item.id)) { item.reject(new Error("任务已取消")); } else { item.resolve(result); } } catch (error) { if (!isCancelled(key, item.id)) { item.reject(error); } } finally { runningTaskIds.delete(key); // 执行完后清除 pending 记录 (虽然在开始执行前其实已经不在 pending 里了,但为了保险) if (((_a = pendingDebounceItem.get(key)) === null || _a === void 0 ? void 0 : _a.id) === item.id) { pendingDebounceItem.delete(key); } checkAndCleanQueue(key); } }; /** * 队列请求方法 * @param fn 数据请求函数 * @param options 配置项 (包含 key, duration) */ function queue(fn, options) { const { key = "default-queue", duration } = options !== null && options !== void 0 ? options : {}; // 参数验证 if (!key || typeof key !== "string") { return Promise.reject(new Error("队列 key 必须是有效的字符串")); } if (typeof fn !== "function") { return Promise.reject(new Error("任务必须是一个返回 Promise 的函数")); } return new Promise((resolve, reject) => { var _a; const id = Symbol(`${key}-${Date.now()}-${Math.random()}`); // 确定是否启用防抖模式及时间 const isDebounceMode = typeof duration === "number" || duration === true; let debounceTime = 0; if (typeof duration === "number") { // 验证防抖时间必须是非负数 if (duration < 0) { reject(new Error("防抖时间不能为负数")); return; } debounceTime = duration; } else if (duration === true) { debounceTime = DEFAULT_DEBOUNCE_TIME; } // 初始化取消集合 if (!cancelledIds.has(key)) { cancelledIds.set(key, new Set()); } if (isDebounceMode) { // 防抖模式: // 1. 标记之前的任务为取消 (包括正在 pending 的和正在执行的) markPreviousAsCancelled(key, id); const item = { id, task: fn, resolve, reject }; // 2. 设置定时器 item.timer = setTimeout(() => { var _a; // 定时器结束,执行任务 // 从 pending 中移除 (因为它即将开始执行) if (((_a = pendingDebounceItem.get(key)) === null || _a === void 0 ? void 0 : _a.id) === id) { pendingDebounceItem.delete(key); executeTask(key, item); } }, debounceTime); // 3. 存入 pending // 类型兼容修正: 使 resolve 和 reject 适配 unknown pendingDebounceItem.set(key, { ...item, resolve: (value) => resolve(value), reject }); } else { // 普通串行模式 const queueList = (_a = queues.get(key)) !== null && _a !== void 0 ? _a : []; if (!queues.has(key)) { queues.set(key, queueList); } queueList.push({ id, task: fn, // 类型兼容修正: 使 resolve 和 reject 适配 unknown resolve: (value) => resolve(value), reject }); processQueue(key); } }); } /***/ }), /* 24 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ omitBy; } /* harmony export */ }); /* harmony import */ var _is_object__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(17); /** * 从创建的一个从对象中,排除满足某些条件的属性的属性 * @param obj 要处理的对象 * @param condition 用于判断是否排除属性的条件函数 * @returns 新的对象,排除了满足条件的属性 */ function omitBy(obj, condition) { const result = {}; if (!(0,_is_object__WEBPACK_IMPORTED_MODULE_0__["default"])(obj)) { return result; } for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const value = obj[key]; if (!condition(value, key)) { result[key] = value; } } } return result; } /* // 示例 const sampleObject = { a: 1, b: 2, c: 3, d: 4, }; // 从对象中排除值大于 2 的属性 const result = omitBy(sampleObject, (value) => value > 2); console.log(result); // { a: 1, b: 2 } */ /***/ }), /* 25 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ objectValueToString; } /* harmony export */ }); /** * obj 中所有 value 转换为 string */ function objectValueToString(query) { return Object.fromEntries(Object.entries(query).map(([key, value]) => [ key, String(value) ])); } /***/ }), /* 26 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); class IframeMessage { constructor() { this.iframe = null; this.url = null; // 页面卸载时自动移除 iframe window.addEventListener("beforeunload", () => this.destroy()); } createIframe(url) { this.url = url; this.iframe = document.createElement("iframe"); Object.assign(this.iframe.style, { width: "1px", height: "1px", display: "none", position: "absolute", top: "0", left: "0", zIndex: "-1", border: "none", background: "transparent", pointerEvents: "none", opacity: "0", visibility: "hidden" }); this.iframe.src = url; document.body.append(this.iframe); return this.iframe; } postMessage(message) { var _a; if (this.iframe) { (_a = this.iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage(message, this.url || "*"); } } onMessage(callback) { window.addEventListener("message", (event) => { if (typeof event.data.type !== "string") { return; } callback(event.data); }); } removeMessageListener(callback) { window.removeEventListener("message", callback); } destroy() { var _a; if (this.iframe) { (_a = this.iframe) === null || _a === void 0 ? void 0 : _a.remove(); this.iframe = null; } } } /* harmony default export */ __webpack_exports__["default"] = (IframeMessage); /***/ }), /* 27 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ flattenAndSort; } /* harmony export */ }); /** * 将对象扁平化为键值对并按键排序(确保确定性) * @param obj 要扁平化的对象 * @param prefix 键前缀 * @returns 扁平化的对象 */ function flattenAndSort(obj, prefix = "") { const out = {}; for (const k of Object.keys(obj).sort()) { const val = obj[k]; const key = prefix ? `${prefix}.${k}` : k; if (val && typeof val === "object" && !Array.isArray(val)) { Object.assign(out, flattenAndSort(val, key)); } else { out[key] = val === undefined || val === null ? "" : String(val); } } return out; } /***/ }), /* 28 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ sha256Base64; } /* harmony export */ }); /* harmony import */ var _array_buffer_to_base64__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(29); /** * 🔑 将字符串转换为SHA-256哈希的Base64编码 * @param str 要哈希的字符串 * @returns Promise<string> Base64编码的哈希值 */ async function sha256Base64(str) { try { const enc = new TextEncoder().encode(str); const digest = await crypto.subtle.digest("SHA-256", enc); return (0,_array_buffer_to_base64__WEBPACK_IMPORTED_MODULE_0__["default"])(digest); } catch (error) { console.error("SHA-256 哈希失败:", error); throw new Error("哈希生成失败"); } } /***/ }), /* 29 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ arrayBufferToBase64; } /* harmony export */ }); /** * 将ArrayBuffer转换为Base64字符串 * @param buffer ArrayBuffer * @returns Base64字符串,失败时返回空字符串 */ function arrayBufferToBase64(buffer) { try { if (!buffer || !(buffer instanceof ArrayBuffer)) { console.warn("提供的 ArrayBuffer 无效"); return ""; } const bytes = new Uint8Array(buffer); let binary = ""; // 使用String.fromCharCode,安全且高效 for (const byte of bytes) { // eslint-disable-next-line unicorn/prefer-code-point binary += String.fromCharCode(byte); } return btoa(binary); } catch (error) { console.warn("Base64 编码失败:", error); return ""; } } /***/ }), /* 30 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ deviceAll: function() { return /* reexport safe */ _all__WEBPACK_IMPORTED_MODULE_14__["default"]; }, /* harmony export */ deviceBrowser: function() { return /* reexport safe */ _browser__WEBPACK_IMPORTED_MODULE_1__["default"]; }, /* harmony export */ deviceCpuCores: function() { return /* reexport safe */ _cpu_cores__WEBPACK_IMPORTED_MODULE_7__["default"]; }, /* harmony export */ deviceFeatures: function() { return /* reexport safe */ _features__WEBPACK_IMPORTED_MODULE_10__["default"]; }, /* harmony export */ deviceHardwareConcurrency: function() { return /* reexport safe */ _hardware_concurrency__WEBPACK_IMPORTED_MODULE_9__["default"]; }, /* harmony export */ deviceI18n: function() { return /* reexport safe */ _i18n__WEBPACK_IMPORTED_MODULE_12__["default"]; }, /* harmony export */ deviceLanguage: function() { return /* reexport safe */ _language__WEBPACK_IMPORTED_MODULE_2__["default"]; }, /* harmony export */ deviceLocation: function() { return /* reexport safe */ _location__WEBPACK_IMPORTED_MODULE_5__["default"]; }, /* harmony export */ deviceMemory: function() { return /* reexport safe */ _memory__WEBPACK_IMPORTED_MODULE_8__["default"]; }, /* harmony export */ deviceOnLine: function() { return /* reexport safe */ _onLine__WEBPACK_IMPORTED_MODULE_3__["default"]; }, /* harmony export */ deviceOperatingSystem: function() { return /* reexport safe */ _operating_system__WEBPACK_IMPORTED_MODULE_0__["default"]; }, /* harmony export */ devicePublicIp: function() { return /* reexport safe */ _public_ip__WEBPACK_IMPORTED_MODULE_6__["default"]; }, /* harmony export */ deviceScreen: function() { return /* reexport safe */ _screen__WEBPACK_IMPORTED_MODULE_4__["default"]; }, /* harmony export */ deviceSensor: function() { return /* reexport safe */ _sensor__WEBPACK_IMPORTED_MODULE_11__["default"]; }, /* harmony export */ deviceUa: function() { return /* reexport safe */ _ua__WEBPACK_IMPORTED_MODULE_13__["default"]; } /* harmony export */ }); /* harmony import */ var _operating_system__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(31); /* harmony import */ var _browser__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(33); /* harmony import */ var _language__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(34); /* harmony import */ var _onLine__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(35); /* harmony import */ var _screen__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(36); /* harmony import */ var _location__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(37); /* harmony import */ var _public_ip__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(38); /* harmony import */ var _cpu_cores__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(39); /* harmony import */ var _memory__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(40); /* harmony import */ var _hardware_concurrency__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(41); /* harmony import */ var _features__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(42); /* harmony import */ var _sensor__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(43); /* harmony import */ var _i18n__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(44); /* harmony import */ var _ua__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(45); /* harmony import */ var _all__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(46); /** * 只要修改了 device 文件夹下的文件,都需要在这里导出,然后如果改变了结构等,需要发布一个新 Y 版本 */ /***/ }), /* 31 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": function() { return /* binding */ deviceOperatingSystem; } /* harmony export */ }); /* harmony import */ var _enum__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(32