shared.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. import { Pos } from "@/utils/math.ts";
  2. import { v4 as uuid } from "uuid";
  3. /**
  4. * 四舍五入
  5. * @param num
  6. * @param b 保留位数
  7. * @returns
  8. */
  9. export const round = (num: number, b: number = 2) => {
  10. const scale = Math.pow(10, b);
  11. return Math.round(num * scale) / scale;
  12. };
  13. /**
  14. * 范围取余
  15. * @param num
  16. * @param mod
  17. */
  18. export const rangMod = (num: number, mod: number) => ((num % mod) + mod) % mod;
  19. /**
  20. * 有偏差的indexOf
  21. * @param arr
  22. * @param warp
  23. * @param val
  24. * @returns
  25. */
  26. export const warpIndexOf = (arr: number[], warp: number, val: number) =>
  27. arr.findIndex((num) => val >= num - warp && val <= num + warp);
  28. /**
  29. * 多个函数合并成一个函数
  30. * @param fns
  31. * @returns
  32. */
  33. export const mergeFuns = (...fns: (() => void)[] | (() => void)[][]) => {
  34. return () => {
  35. fns.forEach((fn) => {
  36. if (Array.isArray(fn)) {
  37. fn.forEach((f) => f());
  38. } else {
  39. fn();
  40. }
  41. });
  42. };
  43. };
  44. export const copy = <T>(data: T): T => JSON.parse(JSON.stringify(data));
  45. /**
  46. * 获取数据类型
  47. * @param value
  48. * @returns
  49. */
  50. export const toRawType = (value: unknown): string =>
  51. Object.prototype.toString.call(value).slice(8, -1);
  52. // 是否修改
  53. const _inRevise = (raw1: any, raw2: any, readly: Set<[any, any]>): boolean => {
  54. if (raw1 === raw2) return false;
  55. const rawType1 = toRawType(raw1);
  56. const rawType2 = toRawType(raw2);
  57. if (rawType1 !== rawType2) {
  58. return true;
  59. } else if (
  60. rawType1 === "String" ||
  61. rawType1 === "Number" ||
  62. rawType1 === "Boolean"
  63. ) {
  64. if (rawType1 === "Number" && isNaN(raw1) && isNaN(raw2)) {
  65. return false;
  66. } else {
  67. return raw1 !== raw2;
  68. }
  69. }
  70. const rawsArray = Array.from(readly.values());
  71. for (const raws of rawsArray) {
  72. if (raws.includes(raw1) && raws.includes(raw2)) {
  73. return false;
  74. }
  75. }
  76. readly.add([raw1, raw2]);
  77. if (rawType1 === "Array") {
  78. return (
  79. raw1.length !== raw2.length ||
  80. raw1.some((item1: any, i: number) => _inRevise(item1, raw2[i], readly))
  81. );
  82. } else if (rawType1 === "Object") {
  83. const rawKeys1 = Object.keys(raw1).sort();
  84. const rawKeys2 = Object.keys(raw2).sort();
  85. return (
  86. _inRevise(rawKeys1, rawKeys2, readly) ||
  87. rawKeys1.some((key) => _inRevise(raw1[key], raw2[key], readly))
  88. );
  89. } else if (rawType1 === "Map") {
  90. const rawKeys1 = Array.from(raw1.keys()).sort();
  91. const rawKeys2 = Array.from(raw2.keys()).sort();
  92. return (
  93. _inRevise(rawKeys1, rawKeys2, readly) ||
  94. rawKeys1.some((key) => _inRevise(raw1.get(key), raw2.get(key), readly))
  95. );
  96. } else if (rawType1 === "Set") {
  97. return inRevise(Array.from(raw1.values()), Array.from(raw2.values()));
  98. } else {
  99. return raw1 !== raw2;
  100. }
  101. };
  102. /**
  103. * 查看数据是否被修改
  104. * @param raw1
  105. * @param raw2
  106. * @returns
  107. */
  108. export const inRevise = (raw1: any, raw2: any) =>
  109. _inRevise(raw1, raw2, new Set());
  110. // 防抖
  111. export const debounce = <T extends (...args: any) => any>(
  112. fn: T,
  113. delay: number = 160
  114. ) => {
  115. let timeout: any;
  116. return function (...args: Parameters<T>) {
  117. clearTimeout(timeout);
  118. timeout = setTimeout(() => {
  119. fn.apply(null, args);
  120. }, delay);
  121. };
  122. };
  123. // 防抖
  124. export const frameEebounce = <T extends (...args: any) => any>(fn: T) => {
  125. let count = 0;
  126. return function (...args: Parameters<T>) {
  127. let current = ++count;
  128. requestAnimationFrame(() => {
  129. if (current === count) {
  130. fn.apply(null, args);
  131. }
  132. });
  133. };
  134. };
  135. /**
  136. * 获取数据变化
  137. * @param newIds
  138. * @param oldIds
  139. * @returns
  140. */
  141. export const getChangePart = <T>(newIds: T[], oldIds: T[] = []) => {
  142. const addPort = newIds.filter((newId) => !oldIds.includes(newId));
  143. const delPort = oldIds.filter((oldId) => !newIds.includes(oldId));
  144. const holdPort = oldIds.filter((oldId) => newIds.includes(oldId));
  145. return { addPort, delPort, holdPort };
  146. };
  147. export const flatPositions = (positions: Pos[]) =>
  148. positions.flatMap((p) => [p.x, p.y]);
  149. export const flatToPositions = (coords: number[]) => {
  150. const positions: Pos[] = [];
  151. for (let i = 0; i < coords.length; i += 2) {
  152. positions.push({
  153. x: coords[i],
  154. y: coords[i + 1],
  155. });
  156. }
  157. return positions;
  158. };
  159. export const onlyId = () => uuid();
  160. export const startAnimation = (update: () => void, dur = -1) => {
  161. let isStop = false;
  162. const animation = () => {
  163. requestAnimationFrame(() => {
  164. if (!isStop) {
  165. update();
  166. animation();
  167. }
  168. });
  169. };
  170. animation();
  171. let timeout: any;
  172. if (dur >= 0) {
  173. setTimeout(() => (isStop = true), dur);
  174. }
  175. return () => {
  176. clearTimeout(timeout);
  177. isStop = true;
  178. };
  179. };
  180. export const arrayInsert = <T>(
  181. array: T[],
  182. item: T,
  183. canInsert: (eItem: T, insertItem: T) => boolean
  184. ) => {
  185. let i = 0;
  186. for (i = 0; i < array.length; i++) {
  187. if (canInsert(array[i], item)) {
  188. break;
  189. }
  190. }
  191. array.splice(i, 0, item);
  192. return array;
  193. };
  194. export const asyncTimeout = (time: number) => {
  195. let timeout: any;
  196. let reject: any;
  197. const promise = new Promise<void>((resolve, r) => {
  198. timeout = setTimeout(resolve, time);
  199. reject = r;
  200. }) as Promise<void> & { stop: () => void };
  201. promise.stop = () => {
  202. clearTimeout(timeout);
  203. reject("取消");
  204. };
  205. return promise;
  206. };
  207. export const getResizeCorsur = (level = true, r = 0) => {
  208. r = rangMod(r, 360);
  209. // 上下 "./icons/m_zoom_v.png"
  210. if (level) {
  211. if ((r > 0 && r < 20) || (r > 160 && r <= 200)) {
  212. return "./icons/m_zoom_h.png";
  213. }
  214. if ((r >= 20 && r <= 70) || (r >= 200 && r <= 250)) {
  215. return "./icons/m_zoom.png";
  216. } else if ((r > 70 && r < 110) || (r > 250 && r < 290)) {
  217. return "./icons/m_zoom_v.png";
  218. } else if ((r >= 110 && r <= 160) || (r >= 290 && r <= 340)) {
  219. return "./icons/m_zoom_r.png";
  220. } else {
  221. return "./icons/m_zoom_h.png";
  222. }
  223. } else {
  224. if ((r > 0 && r < 20) || (r > 160 && r <= 200)) {
  225. return "./icons/m_zoom_v.png";
  226. }
  227. if ((r >= 20 && r <= 70) || (r >= 200 && r <= 250)) {
  228. return "./icons/m_zoom_r.png";
  229. } else if ((r > 70 && r < 110) || (r > 250 && r < 290)) {
  230. return "./icons/m_zoom_h.png";
  231. } else if ((r >= 110 && r <= 160) || (r >= 290 && r <= 340)) {
  232. return "./icons/m_zoom.png";
  233. } else {
  234. return "./icons/m_zoom_v.png";
  235. }
  236. }
  237. };
  238. export const diffArrayChange = <T extends Array<any>>(
  239. newItems: T,
  240. oldItems: T
  241. ) => {
  242. const addedItems = [] as unknown as T;
  243. const deletedItems = [] as unknown as T;
  244. for (const item of newItems) {
  245. if (!oldItems.includes(item)) {
  246. addedItems.push(item);
  247. }
  248. }
  249. for (const item of oldItems) {
  250. if (!newItems.includes(item)) {
  251. deletedItems.push(item);
  252. }
  253. }
  254. return {
  255. added: addedItems,
  256. deleted: deletedItems,
  257. };
  258. };
  259. const DMSRG =
  260. /(\d+(?:\.\d+)?)°(?:(\d+(?:\.\d+)?)['|′])?(?:(\d+(?:\.\d+)?)["|″])?$/;
  261. export const dmsCheck = (dms: string) => {
  262. const r = DMSRG.exec(dms);
  263. return r && Number(r[2] || 0) < 60 && (Number(r[3]) || 0) < 60;
  264. };
  265. // 度分秒转经纬度
  266. export const toDigital = (dms: string) => {
  267. const r = DMSRG.exec(dms);
  268. if (r) {
  269. return round(
  270. Number(r[1]) + Number(r[2] || 0) / 60 + (Number(r[3]) || 0) / 3600,
  271. 12
  272. );
  273. }
  274. };
  275. export const analysisGPS = (temp: string) => {
  276. const getGPS = (t: any[]) => {
  277. t = t.map((d) => (dmsCheck(d) ? toDigital(d) : Number(d)));
  278. if (t.length > 1 && t.every((v) => !Number.isNaN(v))) {
  279. return {
  280. x: t[0],
  281. y: t[1],
  282. } as Pos;
  283. }
  284. };
  285. function isValidWGS84(lon: number, lat: number) {
  286. const isValidLon = lon >= -180 && lon <= 180;
  287. const isValidLat = lat >= -90 && lat <= 90;
  288. return isValidLon && isValidLat;
  289. }
  290. const splitChars = [",", " ", ","];
  291. for (const splitChar of splitChars) {
  292. const gps = getGPS(temp.split(splitChar));
  293. if (gps && isValidWGS84(gps.x, gps.y)) {
  294. return gps;
  295. }
  296. }
  297. };
  298. export const tempStrFill = (tempStr: string, fill: Record<string, any>) => {
  299. const regex = /\{(.*?)\}/g;
  300. let str = "";
  301. let ndx = 0;
  302. let matches;
  303. while ((matches = regex.exec(tempStr)) !== null) {
  304. if (!(matches[1] in fill)) continue;
  305. str += tempStr.substring(ndx, matches.index) + fill[matches[1]];
  306. ndx = matches.index + matches[0].length;
  307. }
  308. str += tempStr.substring(ndx, tempStr.length);
  309. return str;
  310. };
  311. export const genBound = () => {
  312. let seted = false;
  313. let minX = Number.MAX_VALUE,
  314. minY = Number.MAX_VALUE,
  315. maxX = Number.MIN_VALUE,
  316. maxY = Number.MIN_VALUE;
  317. return {
  318. update(points: Pos | Pos[]) {
  319. points = Array.isArray(points) ? points : [points];
  320. points.forEach((pos) => {
  321. seted = true;
  322. minX = Math.min(pos.x, minX);
  323. minY = Math.min(pos.y, minY);
  324. maxX = Math.max(pos.x, maxX);
  325. maxY = Math.max(pos.y, maxY);
  326. });
  327. },
  328. get() {
  329. if (!seted) return null;
  330. return {
  331. x: minX,
  332. y: minY,
  333. width: maxX - minX,
  334. height: maxY - minY,
  335. maxX,
  336. maxY,
  337. center: {
  338. x: (minX + maxX) / 2,
  339. y: (minY + maxY) / 2,
  340. },
  341. };
  342. },
  343. };
  344. };
  345. export const validNum = (num: any) =>
  346. typeof num === "number" && !Number.isNaN(num);
  347. // 检测字体是否可用(返回第一个支持的字体)
  348. export function getSupportedFont(fonts: string[]) {
  349. const canvas = document.createElement("canvas");
  350. const ctx = canvas.getContext("2d")!;
  351. const defaultFont = ctx.font;
  352. for (const font of fonts) {
  353. ctx.font = `12px ${font}`;
  354. if (ctx.font.includes(font)) {
  355. ctx.font = defaultFont; // 恢复默认
  356. return font;
  357. }
  358. }
  359. ctx.font = defaultFont;
  360. return fonts[fonts.length - 1]; // 返回最后一个回退字体
  361. }
  362. export const repeatedlyOnly = <T extends (...args: any[]) => any>(fn: T) => {
  363. let empty = uuid();
  364. let prevResult: any = empty;
  365. const pack = (...args: any[]) => {
  366. if (prevResult === empty) {
  367. const result = fn(...args);
  368. if (result instanceof Promise) {
  369. prevResult = result;
  370. return result.then((r) => {
  371. prevResult = empty;
  372. return r;
  373. });
  374. } else {
  375. return result;
  376. }
  377. } else {
  378. return prevResult;
  379. }
  380. };
  381. return pack as T;
  382. };
  383. export const genCache = <T, R>(
  384. getKey: (args: T) => string | number,
  385. calcResult: (args: T) => R,
  386. delay: number | null = null
  387. ) => {
  388. const cache: Record<string, R> = {};
  389. return (args: T): R => {
  390. const key = getKey(args);
  391. if (key in cache) {
  392. return cache[key];
  393. }
  394. const result = (cache[key] = calcResult(args));
  395. if (delay !== null) {
  396. setTimeout(() => {
  397. delete cache[key];
  398. }, delay);
  399. }
  400. return result;
  401. };
  402. };
  403. export const hoverManage = <T, K>(
  404. hoverHandler: (data: T) => (data: K) => void
  405. ) => {
  406. let enter = false;
  407. let timeout: any;
  408. let leave: undefined | ((data: K) => void);
  409. const enterHandler = (data: T) => {
  410. clearTimeout(timeout);
  411. if (!enter) {
  412. enter = true;
  413. leave = hoverHandler(data);
  414. }
  415. };
  416. const immediatelyLeave = (data: K) => {
  417. enter = false;
  418. leave && leave(data);
  419. };
  420. const leaveHandler = (data: K) => {
  421. timeout = setTimeout(() => {
  422. immediatelyLeave(data);
  423. }, 160);
  424. };
  425. return {
  426. enterHandler,
  427. leaveHandler,
  428. immediatelyLeave,
  429. };
  430. };
  431. export const trackFlag = (trackCallback?: (flag: number) => void) => {
  432. let flag = 0;
  433. const callback = () => {
  434. flag--
  435. trackCallback && trackCallback(flag)
  436. }
  437. return {
  438. get flag() {
  439. return flag
  440. },
  441. track<T>(fn: () => T): T {
  442. flag++;
  443. const result = fn();
  444. if (result instanceof Promise) {
  445. return result.then((data) => {
  446. console.log('callback')
  447. callback()
  448. return data
  449. }) as T;
  450. } else {
  451. callback();
  452. return result
  453. }
  454. },
  455. };
  456. };