class Tools { /** * 判断一个对象是否为 undefined * @param obj 对象 * @returns 对象是否为 undefined */ public static isUndefined(obj: any): boolean { return typeof obj === 'undefined'; } /** * 判断一个对象是否为 null 或者 undefined * @param obj 对象 * @returns 对象是否为 null 或者 undefined */ public static isUndefinedOrNull(obj: any): boolean { return obj === null || typeof obj === 'undefined'; } /** * 判断一个对象是否为 null 或者 undefined 或者空字符串 * @param obj 对象 * @returns 对象是否为 null 或者 undefined 或者 空字符串 */ public static isEmpty(obj: any): boolean { return obj == null || typeof obj == 'undefined' || obj == ''; } /** * 判断一个对象是否是对象类型 * @param obj 对象 * @returns 对象是否是对象类型 */ public static isObject(obj: any): boolean { return !Tools.isEmpty(obj) && typeof obj === 'object'; } /** * 判断一个对象是否是数组 * @param obj 对象 * @returns 对象是否是数组 */ public static isArray(obj: any): boolean { return !Tools.isEmpty(obj) && Array.isArray(obj); } /** * 判断给定的日期是否是当前日期 * @param date 日期 * @returns 判断日期是否是当前日期 */ public static isCurrentDay(date: Date): boolean { return new Date().toISOString().slice(0, 10) === date.toISOString().slice(0, 10); } /** * 判断一个日期是否在两个日期之间 * @param min 下边界日期 * @param max 上边界日期 * @param date 日期 * @returns 判断日期是否在两个日期之间 */ public static isBetweenTwoDates(min: Date, max: Date, date: Date): boolean { return date.getTime() >= min.getTime() && date.getTime() <= max.getTime(); } /** * 判断一个日期是否在周末 * @param date 日期 * @returns 日期是否在周末 */ public static isWeekend(date: Date): boolean { return date.getDay() === 6 || date.getDay() === 0; } /** * 判断一个日期是否在某年内 * @param date 日期 * @param year 年 * @returns 日期是否在某年内 */ public static isInAYear(date: Date, year: number): boolean { return date.getUTCFullYear() === new Date(`${year}`).getUTCFullYear(); } /** * 将 24 小时转换为 am. 或 pm. 格式 * @param h 小时 * @returns 转换后的字符串 */ public static toAMPMFormat(h: number): string { return `${h % 12 === 0 ? 12 : h % 12}${h < 12 ? ' am.' : ' pm.'}`; } /** * 将句子的第一个字母大写 * @param param0 字符串 * @returns 第一个字母大写后的字符串 */ public static capitalize([first, ...rest]: any): string { return `${first.toUpperCase()}${rest.join('')}`; } /** * 将句子的第一个字母小写 * @param param0 字符串 * @returns 第一个字母小写后的字符串 */ public static lowercaseFirst([first, ...rest]: any): string { return `${first.toLowerCase()}${rest.join('')}`; } /** * 将英文字母转成对应的 emoji 形式 * @param c 字母 * @returns emoji 字母 */ public static letterToEmoji(c: string): string { return String.fromCodePoint(c.toLowerCase().charCodeAt(0) + 127365); } /** * 判断一个字符串是不是回文 * @param str 字符串 * @returns 字符串是不是回文 */ public static isPalindrome(str: string): boolean { return str.toLowerCase() === str.toLowerCase().split('').reverse().join(''); } /** * 计算一个数的阶乘 * @param n 数 * @returns 一个数的阶乘 */ public static getFactorial(n: number): number { return n <= 1 ? 1 : n * Tools.getFactorial(n - 1); } /** * 计算一个斐波那契数列第 N 项 * @param n N 项 * @param memo * @returns */ public static getFibonacci(n: number, memo: number[]): number { return memo[n] || (n <= 2 ? 1 : (memo[n] = Tools.getFibonacci(n - 1, memo) + Tools.getFibonacci(n - 2, memo))); } /** * 复制数组 * @param arr 数组 * @returns 复制后的数组 */ public static copyToArray(arr: any[]): any[] { return [...arr]; } /** * 数组去重 * @param arr 数组 * @returns 去重后的数组 */ public static getUnique(arr: any[]): any[] { return [...new Set(arr)]; } /** * 生成随机数字数组 * @param arr 数组 * @returns 生成随机数字数组 */ public static shuffle(arr: number[]): number[] { return arr.sort(() => Math.random() - 0.5); } /** * 反转字符串 * @param str 字符串 * @returns 反转后的字符串 */ public static reverseString(str: string): string { return str.split('').reverse().join(''); } /** * 检查两个数组是否包含相同的值 * @param arr1 第一个数组 * @param arr2 第二个数组 * @returns 两个数组是否包含相同的值 */ public static containSameValues(arr1: any[], arr2: any[]): boolean { return arr1.sort().join(',') === arr2.sort().join(','); } /** * 温度转换(摄氏度->华氏度) * 华氏度 = 32 + 摄氏度× 1.8 * @param celsius 摄氏度 * @returns 华氏度 */ public static toFahrenheit(celsius: number): number { return (celsius * 9) / 5 + 32; } /** * 温度转换(华氏度->摄氏度) * 华氏度 = 32 + 摄氏度× 1.8 * @param fahrenheit 华氏度 * @returns 摄氏度 */ public static toCelsius(fahrenheit: number): number { return ((fahrenheit - 32) * 5) / 9; } /** * 清除浏览器中的所有 cookie * @returns void */ public static clearAllCookies(): void { document.cookie.split(';').forEach((c) => (document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`))); } /** * 检查函数是否为异步函数 * @param f 函数 * @returns 函数是否为异步函数 */ public static isAsyncFunction(f: any): boolean { return Object.prototype.toString.call(f) === '[object AsyncFunction]'; } /** * 判断代码是否在浏览器中运行 * @returns 代码是否在浏览器中运行 */ public static runningInBrowser(): boolean { return typeof window === 'object' && typeof document === 'object'; } /** * 判断代码是否在 Node 中运行 * @returns 代码是否在 Node 中运行 */ public static runningInNode(): boolean { return typeof process !== 'undefined' && process.versions != null && process.versions.node != null; } /** * 判断系统是否是暗模式 * @returns 系统是否是暗模式 */ public static isDarkMode(): boolean { return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; } /** * 将 dom 元素滚动到顶部 * @param element dom 元素 * @returns void */ public static toTop(element: HTMLElement): void { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } /** * 将 dom 元素滚动到底部 * @param element dom 元素 * @returns void */ public static toBottom(element: HTMLElement): void { element.scrollIntoView({ behavior: 'smooth', block: 'end' }); } /** * 将 JSON 转换为 MAP * @param json json 字符串 * @returns MAP */ public static jsonToMap(json: string): Map { return new Map(Object.entries(JSON.parse(json))); } /** * 对象转 json 字符串 * @param obj 对象 * @returns json 字符串 */ public static object2Json(obj: any): string | null { return Tools.isEmpty(obj) ? null : JSON.stringify(obj); } /** * json 字符串转对象 * @param json json 字符串 * @returns 对象 */ public static json2Object(json: string): any { return Tools.isEmpty(json) ? null : JSON.parse(json); } /** * 通过连接字符串连接数组 * 使用说明: * const array =['001','002','003']; * const joined =join(array); // joined='001-002-003' * * const objArray =[{name:'001',age:10},{name:'002',age:20}]; * const joined =join(array,'-','name'); // joined='001-002' * @param array 数组 * @param joiner 连接字符串 * @param propertyName 属性名, 可选参数, 如果数组元素为 js 对象,可通过该参数指定需要连接的 js 对象的属性,如果传入 null ,则直接连接数组元素 * @returns 通过连接字符串连接数组 */ public static join(array: any, joiner: string, propertyName?: string): string { if (Tools.isEmpty(array)) { return ''; } propertyName = propertyName || ''; if (Array.isArray(array)) { let result = ''; if (array.length > 0) { for (let i = 0; i < array.length; i++) { if (propertyName == '') { result += array[i] + joiner; } else { result += array[i][propertyName] + joiner; } } } if (result != '') { result = result.substring(0, result.length - 1); } return result; } else { return array; } } /** * 深度克隆对象 * @param target 需要克隆对象 * @returns 克隆后的对象 */ public static deepClone(target: any): any { const map = new WeakMap(); function isObject(target: any) { return (typeof target === 'object' && target) || typeof target === 'function'; } function clone(data: any) { if (!isObject(data)) { return data; } if ([Date, RegExp].includes(data.constructor)) { return new data.constructor(data); } if (typeof data === 'function') { return new Function('return ' + data.toString())(); } const exist = map.get(data); if (exist) { return exist; } if (data instanceof Map) { const result = new Map(); map.set(data, result); data.forEach((val, key) => { if (isObject(val)) { result.set(key, clone(val)); } else { result.set(key, val); } }); return result; } if (data instanceof Set) { const result = new Set(); map.set(data, result); data.forEach((val) => { if (isObject(val)) { result.add(clone(val)); } else { result.add(val); } }); return result; } const keys = Reflect.ownKeys(data); const allDesc = Object.getOwnPropertyDescriptors(data); const result = Object.create(Object.getPrototypeOf(data), allDesc); map.set(data, result); keys.forEach((key) => { const val = data[key]; if (isObject(val)) { result[key] = clone(val); } else { result[key] = val; } }); return result; } return clone(target); } /** * 构建 Http Get 请求查询参数 url * 使用说明: * 1. 键值对数组 * const params =[ * {key:'name',value:'姓名'}, * {key:'年龄',value:20} * ]; * const url =buildHttpQueryString(params); //url ='name=%E5%A7%93%E5%90%8D&%E5%B9%B4%E9%BE%84=20' * 2. 简单对象 * const params ={ * name:'姓名', * 年龄:20 * }; * const url =buildHttpQueryString(params); //url ='name=%E5%A7%93%E5%90%8D&%E5%B9%B4%E9%BE%84=20' * 3. 对象属性也支持数组 * const params ={ * name:'姓名', * phone:[ * '13012345678', * '13812345678' * ] * }; * const url =buildHttpQueryString(params); //url ='name=%E5%A7%93%E5%90%8D&phone=13012345678,13812345678' * @param parameters 查询参数 * @param encode 编码字符集 * @returns 编码后的 url */ public static buildHttpQueryString(parameters: any, encode?: true): string | null { if (Tools.isEmpty(parameters)) return null; const _parameters: any[] = []; if (Tools.isArray(parameters)) { //支持以 [{key:'name',value:'姓名'},{key:'age',value:20}] 形式的参数 for (let i = 0; i < parameters.length; i++) { if (encode) { _parameters[i] = encodeURIComponent(parameters[i].key) + '=' + encodeURIComponent(parameters[i].value); } else { _parameters[i] = parameters[i].key + '=' + parameters[i].value; } } return Tools.join(_parameters, '&'); } else if (Tools.isObject(parameters)) { //支持以 {name:'姓名',age:20} 形式的参数 let i = 0; for (const key in parameters) { const value = parameters[key]; if (Tools.isArray(value)) { //支持以 {address:['上海','beijing']} 形式的参数 for (let j = 0; j < value.length; j++) { if (encode) { value[j] = encodeURIComponent(value[j]); } } if (encode) { _parameters[i++] = encodeURIComponent(key) + '=' + Tools.join(value, ','); } else { _parameters[i++] = key + '=' + Tools.join(value, ','); } } else { if (encode) { _parameters[i++] = encodeURIComponent(key) + '=' + encodeURIComponent(parameters[key]); } else { _parameters[i++] = key + '=' + parameters[key]; } } } return Tools.join(_parameters, '&'); } else { return null; } } /** * 连接两个 URL 组成一个 URL * @param url1 第一个 URL * @param url2 第二个 URL */ public static concatUrl(url1: string | null, url2: string | null): string { return (Tools.removeUrlSuffixSlash(url1) || '') + '/' + (Tools.removeUrlPrefixSlash(url2) || ''); } /** * 移除 URL 中所有的前导 / * @param url url * @returns 移除后的 URL */ public static removeUrlPrefixSlash(url: string | null): string | null { if (url) { let _url = url; while (_url.startsWith('/')) { _url = _url.substring(1); } return _url; } return null; } /** * 移除 URL 中所有的后导 / * @param url url * @returns 移除后的 URL */ public static removeUrlSuffixSlash(url: string | null): string | null { if (url) { let _url = url; while (_url.endsWith('/')) { _url = _url.substring(0, _url.length - 1); } return _url; } return null; } /** * 给 dom 元素增加 class * @param target dom 元素 * @param className class 名 */ public static addClassName(target: HTMLElement | null, className: string | null): void { if (target && className) { const _class = target.getAttribute('class'); if (_class) { const classes = _class.split(' '); classes.push(className); const clazzSet = [...new Set(classes)]; target.setAttribute('class', clazzSet.join(' ')); } else { target.setAttribute('class', className); } } } /** * 移除 dom 元素的 class * @param target dom 元素 * @param className class 名 */ public static removeClassName(target: HTMLElement | null, className: string | null): void { if (target && className) { const _class = target.getAttribute('class'); if (_class) { let classes = _class.split(' '); classes = [...new Set(classes)]; classes = classes.filter((item) => item !== className); target.setAttribute('class', classes.join(' ')); } } } /** * 获取页面所有外部引用的 javascript 的 url * @returns 页面所有外部引用的 javascript 的 url */ public static getJavascriptElementUrls(): string[] { const result: string[] = []; const scripts = document.getElementsByTagName('script'); for (const script of scripts) { const url = script.getAttribute('src'); if (url) { result.push(url); } } return result; } /** * 在 dom 中插入 标签元素 * @param src javascript url * @param target 插入到 dom 中的元素, 如果未指定则插入到 dom 的 head 元素中 * @param callback 加载成功后回调函数 */ public static appendJavascriptTag(src: string | null, target?: HTMLElement, callback?: any): void { if (src) { const script = document.createElement('script') as HTMLScriptElement; script.type = 'text/javascript'; script.src = src; if (callback) { script.onload = callback; } if (target) { target.appendChild(script); } else { document.head.appendChild(script); } } } /** * 下载 URL, 创建 iframe, 并在 iframe 中下载资源, 避免页面跳转 * @param url URL */ public static download(url: string | null): void { if (url) { const iframeId = '_download_iframe'; let iframe = document.getElementById(iframeId) as HTMLIFrameElement; if (iframe) { iframe.src = url; } else { iframe = document.createElement('iframe'); iframe.id = iframeId; iframe.src = url; iframe.style.display = 'none'; document.getElementsByTagName('body')[0].appendChild(iframe); } } } /** * 设置页面标题 * @param title 页面标题 */ public static setTitile(title: string | null): void { if (title) { document.title = title; } } /** * 设置页面 icon * @param iconUrl 页面 icon url 地址 */ public static setFavicon(favicon: string | null): void { if (favicon) { let faviconElement: HTMLLinkElement = document.querySelector("link[rel*='icon']") as HTMLLinkElement; if (faviconElement) { console.log(faviconElement.href); faviconElement.href = favicon; } else { faviconElement = document.createElement('link'); faviconElement.rel = 'shortcut icon'; faviconElement.href = favicon; document.getElementsByTagName('head')[0].appendChild(faviconElement); } } } /** * 移除 dom 元素 * @param element dom 元素对象或者 dom 元素 ID */ public static removeDomElement(element: HTMLElement | string): void { if (element) { if (Tools.isObject(element)) { (element as HTMLElement).parentNode?.removeChild(element as HTMLElement); } else { const e = document.getElementById(element as string); if (e) { e.parentNode?.removeChild(e); } } } } /** * 返回在一个数值范围内的数值 * @param value 数值 * @param min 数值范围下界 * @param max 数值范围上界 * @returns 在数值范围内的数值 */ public static range(value: number, min: number, max: number): number { if (value < min) { return min; } else if (value > max) { return max; } else { return value; } } /** * 合并对象,将源对象的属性合并到目标对象上 * @param target 目标对象 * @param source 源对象 */ public static mergeObject(target: object, source: object): object { if (source && target) { for (const property in source) { const value = source[property]; if (Tools.isObject(value) && !Tools.isArray(value)) { //正常对象,排除 undefined 和 null if (Tools.isUndefinedOrNull(target[property])) { target[property] = {}; } Tools.mergeObject(target[property], value); } else { if (!Tools.isUndefinedOrNull(value)) { target[property] = value; } } } } return target; } public static objectValueEquals(o1: object | null | undefined, o2: object | null | undefined): boolean { if (Tools.isUndefinedOrNull(o1) && Tools.isUndefinedOrNull(o2)) { return true; } if ((!Tools.isUndefinedOrNull(o1) && Tools.isUndefinedOrNull(o2)) || (Tools.isUndefinedOrNull(o1) && !Tools.isUndefinedOrNull(o2))) { return false; } if (o1 === o2) { return true; } for (const p1 in o1) { if (o1[p1] != o2[p1]) { return false; } } for (const p2 in o2) { if (o1[p2] != o2[p2]) { return false; } } return true; } } export { Tools };