import { Base64 } from "js-base64"; // 节流 export const throttle = any>(fn: T, delay: number = 160) => { let previous = 0; return function , This>(this: This, ...args: Parameters) { const now = Date.now(); if (now - previous >= delay) { fn.apply(this, args); previous = now; } }; }; // 防抖 export const debounce = any>(fn: T, delay: number = 160) => { let timeout: any; return function (this: This, ...args: Parameters) { clearTimeout(timeout); timeout = setTimeout(() => { fn.apply(this, args); }, delay); }; }; const place = /(?:\/:([^/]*))/g; // 匹配 /:id 是否匹配某个url export const equalUrl = (tempUrl: string, url: string) => { const urlRgStr = "^" + tempUrl.replace(/\/([^:])/g, (_, d) => `\\/${d}`).replace(place, () => `(?:/[^/]*)`) + "$"; return new RegExp(urlRgStr).test(url); }; // 生成/:id 类真实url export const gendUrl = (tempUrl: string, params: { [key: string]: any }) => { let url = ""; let preIndex = 0; let m; while ((m = place.exec(tempUrl))) { url += tempUrl.substring(preIndex, m.index + 1) + (params[m[1]] || "null"); preIndex = m.index + m[0].length; } url += tempUrl.substr(preIndex); return url; }; // 匹配一组数组是否是path链接 export const includesUrl = (tempUrls: Array | readonly string[], url: string) => tempUrls.some((tempUrl) => equalUrl(tempUrl, url)); // 字符串转params对象 export const strToParams = (str: string) => { if (str[0] === "?") { str = str.substr(1); } const result: { [key: string]: string } = {}; const splitRG = /([^=&]+)(?:=([^&]*))?&?/; let rgRet; while ((rgRet = str.match(splitRG))) { result[rgRet[1]] = rgRet[2] === undefined ? true : rgRet[2]; str = str.substr(rgRet[0].length); } return result; }; // 对象转params export const paramsToStr = (params: { [key: string]: string | boolean }) => "?" + Object.keys(params) .filter((key) => params[key] !== undefined) .map((key) => `${key}${params[key] == false ? "" : `=${params[key]}`}`) .join("&"); export const objectToString = Object.prototype.toString; export const toTypeString = (value: unknown): string => objectToString.call(value); export const toRawType = (value: unknown): string => { // extract "RawType" from strings like "[object RawType]" return toTypeString(value).slice(8, -1); }; export const isString = (value: T): T extends string ? true : false => (toRawType(value) === "String") as any; // 递归复制 export const recursionCopy = < T extends object | Array, R extends object | Array >( from: R, to: T ): T & R => { if (toRawType(from) !== toRawType(to)) { return to as T & R; } const toKeys = Object.keys(to); const fromKeys = Object.keys(from); const result = toRawType(from) === "Array" ? [...(from as Array)] : { ...from }; for (const toKey of toKeys) { const toItem = (to as any)[toKey]; let i = 0; for (; i < fromKeys.length; i++) { const fromKey = fromKeys[i]; if (toKey === fromKey) { const fromItem = (from as any)[fromKey]; const fromItemType = toRawType(fromItem); const toItemType = toRawType(toItem); if ( fromItemType === toItemType && (fromItemType === "Object" || fromItemType === "Array") ) { (result as any)[fromKey] = recursionCopy(fromItem, toItem); } else { (result as any)[fromKey] = toItem; } break; } } if (i === fromKeys.length) { (result as any)[toKey] = toItem; } } return result as T & R; }; // 日期格式化 export const formatDate = (date: Date, fmt: string = "yyyy-MM-dd hh:mm") => { const map = { "M+": date.getMonth() + 1, //月份 "d+": date.getDate(), //日 "h+": date.getHours(), //小时 "m+": date.getMinutes(), //分 "s+": date.getSeconds(), //秒 "q+": Math.floor((date.getMonth() + 3) / 3), //季度 S: date.getMilliseconds(), //毫秒 }; if (/(y+)/.test(fmt)) { fmt = fmt.replace( RegExp.$1, date .getFullYear() .toString() .substr(4 - RegExp.$1.length) ); } (Object.keys(map) as Array).forEach((k) => { if (new RegExp("(" + k + ")").test(fmt)) { const val = map[k].toString(); fmt = fmt.replace( RegExp.$1, RegExp.$1.length === 1 ? val : ("00" + val).substr(val.length) ); } }); return fmt; }; // 函数调用拦截 export const funIntercept = any>( originFun: T, proxyFun: (args: Parameters, result: ReturnType) => void ): T => function (...args: Parameters) { const result = originFun.apply(this, args); proxyFun(args, result); return result; } as T; // 四舍五入保留指定位数 export const round = (num: number, index: number = 2) => { const s = Math.pow(10, index); return Math.round(num * s) / s; }; // setTimeout转promise export const asyncTimeout = (mis: number = 0) => new Promise((resolve) => setTimeout(resolve, mis)); // 持续检查直到通过 export const checkPromise = (check: () => T) => { return new Promise((resolve) => { const interval = setInterval(() => { const result = check(); if (!!result) { clearInterval(interval); resolve(result); } }, 16); }); }; // 下载文件 // export const downFile = (url: string, name?: string) => { // console.log(saveAs(url, name)) // // const el = document.createElement('a') // // el.setAttribute('download', name) // // el.setAttribute('href', url) // // el.click() // } export const copyText = async (text: string, fallback?: boolean) => { if (navigator.clipboard && !fallback) { let permiss; try { let permiss = await navigator.permissions.query({ name: "geolocation" }); permiss.state === "denied"; } catch (e) { console.error(e); } if (permiss && permiss.state === "denied") { console.error(permiss); throw new Error("请授予写入粘贴板权限!"); } else { try { await navigator.clipboard.writeText(text); } catch (e) { console.error("不支持navigator.clipboard.writeText 开启回退"); return await copyText(text, true); } } } else { const textarea = document.createElement("textarea"); document.body.appendChild(textarea); // 隐藏此输入框 textarea.style.position = "fixed"; textarea.style.clip = "rect(0 0 0 0)"; textarea.style.top = "10px"; // 赋值 textarea.value = text; // 选中 textarea.select(); // 复制 document.execCommand("copy", true); // 移除输入框 document.body.removeChild(textarea); } }; // 是否修改 const _inRevise = (raw1, raw2, readly: Set<[any, any]>) => { if (raw1 === raw2) return false; const rawType1 = toRawType(raw1); const rawType2 = toRawType(raw2); if (rawType1 !== rawType2) { return true; } else if (rawType1 === "String" || rawType1 === "Number" || rawType1 === "Boolean") { if (rawType1 === "Number" && isNaN(raw1) && isNaN(raw2)) { return false; } else { return raw1 !== raw2; } } const rawsArray = Array.from(readly.values()); for (const raws of rawsArray) { if (raws.includes(raw1) && raws.includes(raw2)) { return false; } } readly.add([raw1, raw2]); if (rawType1 === "Array") { return ( raw1.length !== raw2.length || raw1.some((item1, i) => _inRevise(item1, raw2[i], readly)) ); } else if (rawType1 === "Object") { const rawKeys1 = Object.keys(raw1).sort(); const rawKeys2 = Object.keys(raw2).sort(); return ( _inRevise(rawKeys1, rawKeys2, readly) || rawKeys1.some((key) => _inRevise(raw1[key], raw2[key], readly)) ); } else if (rawType1 === "Map") { const rawKeys1 = Array.from(raw1.keys()).sort(); const rawKeys2 = Array.from(raw2.keys()).sort(); return ( _inRevise(rawKeys1, rawKeys2, readly) || rawKeys1.some((key) => _inRevise(raw1.get(key), raw2.get(key), readly)) ); } else if (rawType1 === "Set") { return inRevise(Array.from(raw1.values()), Array.from(raw2.values())); } else { return raw1 !== raw2; } }; export const inRevise = (raw1, raw2) => _inRevise(raw1, raw2, new Set()); function randomWord(randomFlag, min, max?: number) { let str = ""; let range = min; let arr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", ]; // 随机产生 if (randomFlag) { range = Math.round(Math.random() * (max - min)) + min; } for (var i = 0; i < range; i++) { let pos = Math.round(Math.random() * (arr.length - 1)); str += arr[pos]; } return str; } /** * 密码加密 * @param {String} pwd */ export function encodePassword(str: string, strv = "") { str = Base64.encode(str); const NUM = 2; const front = randomWord(false, 8); const middle = randomWord(false, 8); const end = randomWord(false, 8); let str1 = str.substring(0, NUM); let str2 = str.substring(NUM); if (strv) { let strv1 = strv.substring(0, NUM); let strv2 = strv.substring(NUM); return [front + str2 + middle + str1 + end, front + strv2 + middle + strv1 + end]; } return front + str2 + middle + str1 + end; } export const normalizeLink = (link: string): string => { if (!link.includes("//")) { return location.protocol + "//" + link; } else { return link; } }; // 加载第三方库 export const loadLib = (() => { const cache = {}; const load = (lib: string, success, err, maxReq = 0) => { const el = document.createElement("script"); el.src = lib; document.body.appendChild(el); el.onload = success; el.onerror = () => { if (maxReq > 0) { load(lib, success, err, --maxReq); } else { err(); } }; }; return (lib: string) => { if (!cache[lib]) { cache[lib] = new Promise((resolve, reject) => { load(lib, resolve, reject, 3); }); } return cache[lib]; }; })(); export const numberSplice = (val: number) => { const integer = Math.floor(val); const decimal = val - integer; return [integer, decimal]; }; //经纬度转度°分′秒″ export const toDegrees = (val: number, retain = 4) => { let temps = numberSplice(val); const d = temps[0]; temps = numberSplice(temps[1] * 60); const m = temps[0]; const s = round(temps[1] * 60, retain); return `${d}°${m}′${s}″`; }; export const DMSRG = /(\d+)°(\d+)′(\d+|\d+.\d+)″$/; export const dmsCheck = (dms: string) => { const r = DMSRG.exec(dms); return r && Number(r[2]) < 60 && Number(r[3]) < 60; }; // 度分秒转经纬度 export const toDigital = (dms: string, retain = 6) => { const r = DMSRG.exec(dms); if (r) { return round(Number(r[1]) + Number(r[2]) / 60 + Number(r[3]) / 3600, 12); } }; export const addImmobilityClick = ( dom: HTMLElement, handler: (ev?: MouseEvent) => any ) => { const mousedownHandler = (ev: MouseEvent) => { const dx = ev.offsetX; const dy = ev.offsetY; dom.addEventListener("mouseup", function upHandler(ev) { const ux = ev.offsetX; const uy = ev.offsetY; if (Math.abs(dx - ux + (dy - uy)) < 5) { handler(ev); } }); }; dom.addEventListener("mousedown", mousedownHandler); return () => dom.removeEventListener("mousedown", mousedownHandler); }; export const firstUpperCase = (str: string) => { return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); }; export * from "./file-serve"; export * from "./vue"; export * from "./graph"; export const base64ToBlob = (base64Data: string) => { let arr = base64Data.split(","); let matchs = arr[0].match(/:(.*?);/); if (!matchs) { return null; } let fileType = matchs[1]; let bstr = atob(arr[1]), l = bstr.length, u8Arr = new Uint8Array(l); while (l--) { u8Arr[l] = bstr.charCodeAt(l); } return new Blob([u8Arr], { type: fileType, }); }; export const blobToBase64 = (blob: Blob) => { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = (e) => { resolve(e.target.result as string); }; // readAsDataURL fileReader.readAsDataURL(blob); fileReader.onerror = () => { reject(new Error('blobToBase64 error')); }; }); } export const getId = () => { return (new Date()).getTime().toString() + Math.ceil(Math.random() * 1000).toString() }