index.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. import { Base64 } from "js-base64";
  2. // 节流
  3. export const throttle = <T extends (...args: any) => any>(fn: T, delay: number = 160) => {
  4. let previous = 0;
  5. return function <Args extends Array<any>, This>(this: This, ...args: Parameters<T>) {
  6. const now = Date.now();
  7. if (now - previous >= delay) {
  8. fn.apply(this, args);
  9. previous = now;
  10. }
  11. };
  12. };
  13. // 防抖
  14. export const debounce = <T extends (...args: any) => any>(fn: T, delay: number = 160) => {
  15. let timeout: any;
  16. return function <This>(this: This, ...args: Parameters<T>) {
  17. clearTimeout(timeout);
  18. timeout = setTimeout(() => {
  19. fn.apply(this, args);
  20. }, delay);
  21. };
  22. };
  23. const place = /(?:\/:([^/]*))/g;
  24. // 匹配 /:id 是否匹配某个url
  25. export const equalUrl = (tempUrl: string, url: string) => {
  26. const urlRgStr =
  27. "^" +
  28. tempUrl.replace(/\/([^:])/g, (_, d) => `\\/${d}`).replace(place, () => `(?:/[^/]*)`) +
  29. "$";
  30. return new RegExp(urlRgStr).test(url);
  31. };
  32. // 生成/:id 类真实url
  33. export const gendUrl = (tempUrl: string, params: { [key: string]: any }) => {
  34. let url = "";
  35. let preIndex = 0;
  36. let m;
  37. while ((m = place.exec(tempUrl))) {
  38. url += tempUrl.substring(preIndex, m.index + 1) + (params[m[1]] || "null");
  39. preIndex = m.index + m[0].length;
  40. }
  41. url += tempUrl.substr(preIndex);
  42. return url;
  43. };
  44. // 匹配一组数组是否是path链接
  45. export const includesUrl = (tempUrls: Array<string> | readonly string[], url: string) =>
  46. tempUrls.some((tempUrl) => equalUrl(tempUrl, url));
  47. // 字符串转params对象
  48. export const strToParams = (str: string) => {
  49. if (str[0] === "?") {
  50. str = str.substr(1);
  51. }
  52. const result: { [key: string]: string } = {};
  53. const splitRG = /([^=&]+)(?:=([^&]*))?&?/;
  54. let rgRet;
  55. while ((rgRet = str.match(splitRG))) {
  56. result[rgRet[1]] = rgRet[2] === undefined ? true : rgRet[2];
  57. str = str.substr(rgRet[0].length);
  58. }
  59. return result;
  60. };
  61. // 对象转params
  62. export const paramsToStr = (params: { [key: string]: string | boolean }) =>
  63. "?" +
  64. Object.keys(params)
  65. .filter((key) => params[key] !== undefined)
  66. .map((key) => `${key}${params[key] == false ? "" : `=${params[key]}`}`)
  67. .join("&");
  68. export const objectToString = Object.prototype.toString;
  69. export const toTypeString = (value: unknown): string => objectToString.call(value);
  70. export const toRawType = (value: unknown): string => {
  71. // extract "RawType" from strings like "[object RawType]"
  72. return toTypeString(value).slice(8, -1);
  73. };
  74. export const isString = <T>(value: T): T extends string ? true : false =>
  75. (toRawType(value) === "String") as any;
  76. // 递归复制
  77. export const recursionCopy = <
  78. T extends object | Array<any>,
  79. R extends object | Array<any>
  80. >(
  81. from: R,
  82. to: T
  83. ): T & R => {
  84. if (toRawType(from) !== toRawType(to)) {
  85. return to as T & R;
  86. }
  87. const toKeys = Object.keys(to);
  88. const fromKeys = Object.keys(from);
  89. const result = toRawType(from) === "Array" ? [...(from as Array<any>)] : { ...from };
  90. for (const toKey of toKeys) {
  91. const toItem = (to as any)[toKey];
  92. let i = 0;
  93. for (; i < fromKeys.length; i++) {
  94. const fromKey = fromKeys[i];
  95. if (toKey === fromKey) {
  96. const fromItem = (from as any)[fromKey];
  97. const fromItemType = toRawType(fromItem);
  98. const toItemType = toRawType(toItem);
  99. if (
  100. fromItemType === toItemType &&
  101. (fromItemType === "Object" || fromItemType === "Array")
  102. ) {
  103. (result as any)[fromKey] = recursionCopy(fromItem, toItem);
  104. } else {
  105. (result as any)[fromKey] = toItem;
  106. }
  107. break;
  108. }
  109. }
  110. if (i === fromKeys.length) {
  111. (result as any)[toKey] = toItem;
  112. }
  113. }
  114. return result as T & R;
  115. };
  116. // 日期格式化
  117. export const formatDate = (date: Date, fmt: string = "yyyy-MM-dd hh:mm") => {
  118. const map = {
  119. "M+": date.getMonth() + 1, //月份
  120. "d+": date.getDate(), //日
  121. "h+": date.getHours(), //小时
  122. "m+": date.getMinutes(), //分
  123. "s+": date.getSeconds(), //秒
  124. "q+": Math.floor((date.getMonth() + 3) / 3), //季度
  125. S: date.getMilliseconds(), //毫秒
  126. };
  127. if (/(y+)/.test(fmt)) {
  128. fmt = fmt.replace(
  129. RegExp.$1,
  130. date
  131. .getFullYear()
  132. .toString()
  133. .substr(4 - RegExp.$1.length)
  134. );
  135. }
  136. (Object.keys(map) as Array<keyof typeof map>).forEach((k) => {
  137. if (new RegExp("(" + k + ")").test(fmt)) {
  138. const val = map[k].toString();
  139. fmt = fmt.replace(
  140. RegExp.$1,
  141. RegExp.$1.length === 1 ? val : ("00" + val).substr(val.length)
  142. );
  143. }
  144. });
  145. return fmt;
  146. };
  147. // 函数调用拦截
  148. export const funIntercept = <T extends (...args: any) => any>(
  149. originFun: T,
  150. proxyFun: (args: Parameters<T>, result: ReturnType<T>) => void
  151. ): T =>
  152. function (...args: Parameters<T>) {
  153. const result = originFun.apply(this, args);
  154. proxyFun(args, result);
  155. return result;
  156. } as T;
  157. // 四舍五入保留指定位数
  158. export const round = (num: number, index: number = 2) => {
  159. const s = Math.pow(10, index);
  160. return Math.round(num * s) / s;
  161. };
  162. // setTimeout转promise
  163. export const asyncTimeout = (mis: number = 0) =>
  164. new Promise((resolve) => setTimeout(resolve, mis));
  165. // 持续检查直到通过
  166. export const checkPromise = <T = any>(check: () => T) => {
  167. return new Promise<T>((resolve) => {
  168. const interval = setInterval(() => {
  169. const result = check();
  170. if (!!result) {
  171. clearInterval(interval);
  172. resolve(result);
  173. }
  174. }, 16);
  175. });
  176. };
  177. // 下载文件
  178. // export const downFile = (url: string, name?: string) => {
  179. // console.log(saveAs(url, name))
  180. // // const el = document.createElement('a')
  181. // // el.setAttribute('download', name)
  182. // // el.setAttribute('href', url)
  183. // // el.click()
  184. // }
  185. export const copyText = async (text: string, fallback?: boolean) => {
  186. if (navigator.clipboard && !fallback) {
  187. let permiss;
  188. try {
  189. let permiss = await navigator.permissions.query({ name: "geolocation" });
  190. permiss.state === "denied";
  191. } catch (e) {
  192. console.error(e);
  193. }
  194. if (permiss && permiss.state === "denied") {
  195. console.error(permiss);
  196. throw new Error("请授予写入粘贴板权限!");
  197. } else {
  198. try {
  199. await navigator.clipboard.writeText(text);
  200. } catch (e) {
  201. console.error("不支持navigator.clipboard.writeText 开启回退");
  202. return await copyText(text, true);
  203. }
  204. }
  205. } else {
  206. const textarea = document.createElement("textarea");
  207. document.body.appendChild(textarea);
  208. // 隐藏此输入框
  209. textarea.style.position = "fixed";
  210. textarea.style.clip = "rect(0 0 0 0)";
  211. textarea.style.top = "10px";
  212. // 赋值
  213. textarea.value = text;
  214. // 选中
  215. textarea.select();
  216. // 复制
  217. document.execCommand("copy", true);
  218. // 移除输入框
  219. document.body.removeChild(textarea);
  220. }
  221. };
  222. // 是否修改
  223. const _inRevise = (raw1, raw2, readly: Set<[any, any]>) => {
  224. if (raw1 === raw2) return false;
  225. const rawType1 = toRawType(raw1);
  226. const rawType2 = toRawType(raw2);
  227. if (rawType1 !== rawType2) {
  228. return true;
  229. } else if (rawType1 === "String" || rawType1 === "Number" || rawType1 === "Boolean") {
  230. if (rawType1 === "Number" && isNaN(raw1) && isNaN(raw2)) {
  231. return false;
  232. } else {
  233. return raw1 !== raw2;
  234. }
  235. }
  236. const rawsArray = Array.from(readly.values());
  237. for (const raws of rawsArray) {
  238. if (raws.includes(raw1) && raws.includes(raw2)) {
  239. return false;
  240. }
  241. }
  242. readly.add([raw1, raw2]);
  243. if (rawType1 === "Array") {
  244. return (
  245. raw1.length !== raw2.length ||
  246. raw1.some((item1, i) => _inRevise(item1, raw2[i], readly))
  247. );
  248. } else if (rawType1 === "Object") {
  249. const rawKeys1 = Object.keys(raw1).sort();
  250. const rawKeys2 = Object.keys(raw2).sort();
  251. return (
  252. _inRevise(rawKeys1, rawKeys2, readly) ||
  253. rawKeys1.some((key) => _inRevise(raw1[key], raw2[key], readly))
  254. );
  255. } else if (rawType1 === "Map") {
  256. const rawKeys1 = Array.from(raw1.keys()).sort();
  257. const rawKeys2 = Array.from(raw2.keys()).sort();
  258. return (
  259. _inRevise(rawKeys1, rawKeys2, readly) ||
  260. rawKeys1.some((key) => _inRevise(raw1.get(key), raw2.get(key), readly))
  261. );
  262. } else if (rawType1 === "Set") {
  263. return inRevise(Array.from(raw1.values()), Array.from(raw2.values()));
  264. } else {
  265. return raw1 !== raw2;
  266. }
  267. };
  268. export const inRevise = (raw1, raw2) => _inRevise(raw1, raw2, new Set());
  269. function randomWord(randomFlag, min, max?: number) {
  270. let str = "";
  271. let range = min;
  272. let arr = [
  273. "0",
  274. "1",
  275. "2",
  276. "3",
  277. "4",
  278. "5",
  279. "6",
  280. "7",
  281. "8",
  282. "9",
  283. "a",
  284. "b",
  285. "c",
  286. "d",
  287. "e",
  288. "f",
  289. "g",
  290. "h",
  291. "i",
  292. "j",
  293. "k",
  294. "l",
  295. "m",
  296. "n",
  297. "o",
  298. "p",
  299. "q",
  300. "r",
  301. "s",
  302. "t",
  303. "u",
  304. "v",
  305. "w",
  306. "x",
  307. "y",
  308. "z",
  309. "A",
  310. "B",
  311. "C",
  312. "D",
  313. "E",
  314. "F",
  315. "G",
  316. "H",
  317. "I",
  318. "J",
  319. "K",
  320. "L",
  321. "M",
  322. "N",
  323. "O",
  324. "P",
  325. "Q",
  326. "R",
  327. "S",
  328. "T",
  329. "U",
  330. "V",
  331. "W",
  332. "X",
  333. "Y",
  334. "Z",
  335. ];
  336. // 随机产生
  337. if (randomFlag) {
  338. range = Math.round(Math.random() * (max - min)) + min;
  339. }
  340. for (var i = 0; i < range; i++) {
  341. let pos = Math.round(Math.random() * (arr.length - 1));
  342. str += arr[pos];
  343. }
  344. return str;
  345. }
  346. /**
  347. * 密码加密
  348. * @param {String} pwd
  349. */
  350. export function encodePassword(str: string, strv = "") {
  351. str = Base64.encode(str);
  352. const NUM = 2;
  353. const front = randomWord(false, 8);
  354. const middle = randomWord(false, 8);
  355. const end = randomWord(false, 8);
  356. let str1 = str.substring(0, NUM);
  357. let str2 = str.substring(NUM);
  358. if (strv) {
  359. let strv1 = strv.substring(0, NUM);
  360. let strv2 = strv.substring(NUM);
  361. return [front + str2 + middle + str1 + end, front + strv2 + middle + strv1 + end];
  362. }
  363. return front + str2 + middle + str1 + end;
  364. }
  365. export const normalizeLink = (link: string): string => {
  366. if (!link.includes("//")) {
  367. return location.protocol + "//" + link;
  368. } else {
  369. return link;
  370. }
  371. };
  372. // 加载第三方库
  373. export const loadLib = (() => {
  374. const cache = {};
  375. const load = (lib: string, success, err, maxReq = 0) => {
  376. const el = document.createElement("script");
  377. el.src = lib;
  378. document.body.appendChild(el);
  379. el.onload = success;
  380. el.onerror = () => {
  381. if (maxReq > 0) {
  382. load(lib, success, err, --maxReq);
  383. } else {
  384. err();
  385. }
  386. };
  387. };
  388. return (lib: string) => {
  389. if (!cache[lib]) {
  390. cache[lib] = new Promise((resolve, reject) => {
  391. load(lib, resolve, reject, 3);
  392. });
  393. }
  394. return cache[lib];
  395. };
  396. })();
  397. export const numberSplice = (val: number) => {
  398. const integer = Math.floor(val);
  399. const decimal = val - integer;
  400. return [integer, decimal];
  401. };
  402. //经纬度转度°分′秒″
  403. export const toDegrees = (val: number, retain = 4) => {
  404. let temps = numberSplice(val);
  405. const d = temps[0];
  406. temps = numberSplice(temps[1] * 60);
  407. const m = temps[0];
  408. const s = round(temps[1] * 60, retain);
  409. return `${d}°${m}′${s}″`;
  410. };
  411. export const DMSRG = /(\d+)°(\d+)′(\d+|\d+.\d+)″$/;
  412. export const dmsCheck = (dms: string) => {
  413. const r = DMSRG.exec(dms);
  414. return r && Number(r[2]) < 60 && Number(r[3]) < 60;
  415. };
  416. // 度分秒转经纬度
  417. export const toDigital = (dms: string, retain = 6) => {
  418. const r = DMSRG.exec(dms);
  419. if (r) {
  420. return round(Number(r[1]) + Number(r[2]) / 60 + Number(r[3]) / 3600, 12);
  421. }
  422. };
  423. export const addImmobilityClick = (
  424. dom: HTMLElement,
  425. handler: (ev?: MouseEvent) => any
  426. ) => {
  427. const mousedownHandler = (ev: MouseEvent) => {
  428. const dx = ev.offsetX;
  429. const dy = ev.offsetY;
  430. dom.addEventListener("mouseup", function upHandler(ev) {
  431. const ux = ev.offsetX;
  432. const uy = ev.offsetY;
  433. if (Math.abs(dx - ux + (dy - uy)) < 5) {
  434. handler(ev);
  435. }
  436. });
  437. };
  438. dom.addEventListener("mousedown", mousedownHandler);
  439. return () => dom.removeEventListener("mousedown", mousedownHandler);
  440. };
  441. export const firstUpperCase = (str: string) => {
  442. return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
  443. };
  444. export * from "./file-serve";
  445. export * from "./vue";
  446. export * from "./graph";
  447. export const base64ToBlob = (base64Data: string) => {
  448. let arr = base64Data.split(",");
  449. let matchs = arr[0].match(/:(.*?);/);
  450. if (!matchs) {
  451. return null;
  452. }
  453. let fileType = matchs[1];
  454. let bstr = atob(arr[1]),
  455. l = bstr.length,
  456. u8Arr = new Uint8Array(l);
  457. while (l--) {
  458. u8Arr[l] = bstr.charCodeAt(l);
  459. }
  460. return new Blob([u8Arr], {
  461. type: fileType,
  462. });
  463. };
  464. export const blobToBase64 = (blob: Blob) => {
  465. return new Promise<string>((resolve, reject) => {
  466. const fileReader = new FileReader();
  467. fileReader.onload = (e) => {
  468. resolve(e.target.result as string);
  469. };
  470. // readAsDataURL
  471. fileReader.readAsDataURL(blob);
  472. fileReader.onerror = () => {
  473. reject(new Error('blobToBase64 error'));
  474. };
  475. });
  476. }
  477. export const getId = () => {
  478. return (new Date()).getTime().toString() + Math.ceil(Math.random() * 1000).toString()
  479. }