Utils.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. import {
  2. Prefab,
  3. resources,
  4. Node,
  5. find,
  6. instantiate,
  7. Vec3,
  8. log,
  9. Component,
  10. Button,
  11. warn,
  12. Asset,
  13. error,
  14. v2,
  15. UITransform,
  16. tween,
  17. v3,
  18. Vec2,
  19. director,
  20. ImageAsset,
  21. assetManager,
  22. } from "cc";
  23. import { LanguageManager } from "./LanguageManager";
  24. import BaseUI from "../base/BaseUI";
  25. import PoolMgr from "./PoolMgr";
  26. import DisplayNumber from "./DisplayNumber";
  27. import { UtilTips } from "./UtilTips";
  28. export default class Utils {
  29. public static copyText(text: string) {
  30. navigator.clipboard.writeText(text).then(
  31. (res) => {
  32. console.log(" writeText success: ", res);
  33. // Tips.show(Utils.setI18nLabel("Share.LinkCopied"));
  34. },
  35. (err) => {
  36. console.log(" writeText error: ", err);
  37. this.copyTextFallback(text);
  38. }
  39. );
  40. }
  41. private static copyTextFallback(text) {
  42. const textArea = document.createElement("textarea");
  43. textArea.value = text;
  44. document.body.appendChild(textArea);
  45. textArea.select();
  46. try {
  47. document.execCommand("copy");
  48. UtilTips.show(Utils.setI18nLabel("Share.LinkCopied")); //"Link copied!");
  49. } catch (err) {
  50. UtilTips.show(Utils.setI18nLabel("Share.Title1"));
  51. }
  52. document.body.removeChild(textArea);
  53. }
  54. static sleep(ms: number) {
  55. return new Promise((resolve) => setTimeout(resolve, ms));
  56. }
  57. static padNumber(num, length): string {
  58. let str = num.toString();
  59. while (str.length < length) {
  60. str = "0" + str;
  61. }
  62. return str;
  63. }
  64. static log(o: any) {
  65. if ("1" == this.getQueryString("log")) {
  66. console.log(o);
  67. }
  68. }
  69. // 创建UI
  70. static createUI(
  71. filepath: string,
  72. parent: Node = null,
  73. callback: Function = null
  74. ): Promise<Node> {
  75. return new Promise((resolve, reject) => {
  76. resources.load(filepath, Prefab, (err, ret) => {
  77. log("加载UI:" + filepath);
  78. if (err) {
  79. console.error(err);
  80. reject();
  81. return;
  82. }
  83. if (parent == null) {
  84. parent = find("Canvas/popupNode");
  85. }
  86. let index = filepath.lastIndexOf("/");
  87. let name = filepath.substr(index + 1, filepath.length - index);
  88. if (parent.getComponentInChildren(name)) {
  89. console.log("重复UI跳过");
  90. return;
  91. }
  92. var tmp: Node = instantiate(ret);
  93. tmp.parent = parent;
  94. if (callback) callback(tmp);
  95. resolve(tmp);
  96. });
  97. });
  98. }
  99. // 创建预制体
  100. static createPrefab(
  101. filepath: string,
  102. parent: Node = null,
  103. callback: Function = null,
  104. pos: Vec3 = null
  105. ) {
  106. return new Promise((resolve, reject) => {
  107. resources.load(filepath, Prefab, (err, ret) => {
  108. if (err) {
  109. console.error(err);
  110. reject();
  111. return;
  112. }
  113. if (parent == null) {
  114. parent = find("Canvas");
  115. }
  116. var tmp: Node = instantiate(ret);
  117. // tmp.opacity = 0;
  118. // tmp.runAction(
  119. // sequence(
  120. // delayTime(0.01),
  121. // callFunc(() => {
  122. // tmp.opacity = 255;
  123. // })
  124. // )
  125. // );
  126. tmp.parent = parent;
  127. if (pos) {
  128. tmp.position = pos;
  129. }
  130. if (callback) callback(tmp);
  131. resolve(tmp);
  132. });
  133. });
  134. }
  135. // 获取随机数
  136. public static getRandom(lower, upper): number {
  137. return Math.random() * (upper - lower) + lower;
  138. }
  139. // 获取随机整数
  140. public static getRandomInt(lower, upper): number {
  141. return Math.floor(Math.random() * (upper - lower)) + lower;
  142. }
  143. // 获取随机整数
  144. public static seedRandomInt(lower, upper): number {
  145. return Utils.getRandomInt(lower, upper);
  146. }
  147. // 格式化数字
  148. public static formatNumber(num: number, afterdot: number = 2): string {
  149. num = Number(num);
  150. num = Number(num.toFixed(afterdot));
  151. if (num < 1000) {
  152. return num.toString();
  153. }
  154. return DisplayNumber.displayNumber(num, afterdot);
  155. // return BigNumber.getLargeString(num);
  156. }
  157. public static getPowNum(p) {
  158. return Math.pow(10, p);
  159. }
  160. // 设置服务器时间
  161. public static setServerTime(time: number) {
  162. Utils.timeOffset = time - new Date().getTime();
  163. }
  164. // 获取服务器时间
  165. public static timeOffset: number = 0;
  166. public static getServerTime() {
  167. return new Date().getTime() + Utils.timeOffset;
  168. }
  169. // 添加点击事件
  170. public static addClickEvent(
  171. node,
  172. target,
  173. component,
  174. handler,
  175. customEventData
  176. ) {
  177. var eventHandler = new Component.EventHandler();
  178. eventHandler.target = target;
  179. eventHandler.component = component;
  180. eventHandler.handler = handler;
  181. if (customEventData) eventHandler.customEventData = customEventData;
  182. var clickEvents = node.getComponent(Button).clickEvents;
  183. if (clickEvents.length > 0) {
  184. // if (!EDITOR) warn("按钮已经存在绑定,跳过自动绑定", node.name);
  185. return;
  186. }
  187. console.log(node.name, target.name, component);
  188. clickEvents.push(eventHandler);
  189. }
  190. public static secondsToDHMS(seconds) {
  191. if (seconds <= 0) {
  192. return `00d 00h:00m:00s`;
  193. }
  194. const days = Math.floor(seconds / (24 * 3600));
  195. let d = days < 9 ? "0" + days : days;
  196. const hours = Math.floor((seconds % (24 * 3600)) / 3600);
  197. let h = hours < 9 ? "0" + hours : hours;
  198. const minutes = Math.floor((seconds % 3600) / 60);
  199. let min = minutes < 9 ? "0" + minutes : minutes;
  200. const second = Math.floor(seconds % 60);
  201. let s = second < 9 ? "0" + second : second;
  202. let str = days > 0 ? `${d}d ${h}h:${min}m:${s}s` : `${h}h:${min}m:${s}s`;
  203. return str;
  204. }
  205. public static formatTimestamp(timestamp) {}
  206. // 秒转换为时分秒
  207. public static getToTimeByS(second: number) {
  208. const totalSeconds = Math.floor(second);
  209. const hours = Math.floor(totalSeconds / 3600) % 24;
  210. const minutes = Math.floor((totalSeconds % 3600) / 60);
  211. const seconds = totalSeconds % 60;
  212. return [hours, minutes, seconds]
  213. .map((unit) => (unit < 10 ? "0" + unit : unit.toString())) // 确保每个部分都是两位数
  214. .join(":"); // 拼接为字符串
  215. }
  216. private static padStartCustom(str, targetLength, padString) {
  217. str = String(str);
  218. padString = String(padString);
  219. if (str.length >= targetLength) {
  220. return str;
  221. }
  222. targetLength -= str.length;
  223. if (targetLength > padString.length) {
  224. padString = padString.repeat(Math.ceil(targetLength / padString.length));
  225. }
  226. return padString.slice(0, targetLength) + str;
  227. }
  228. public static formateDateRemaining(targetTimestamp: number) {
  229. const now = Utils.getServerTime();
  230. const remainingTime = targetTimestamp - now;
  231. const days = Math.floor(remainingTime / (1000 * 60 * 60 * 24));
  232. const hours = Math.floor(
  233. (remainingTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
  234. );
  235. const minutes = Math.floor(
  236. (remainingTime % (1000 * 60 * 60)) / (1000 * 60)
  237. );
  238. const seconds = Math.floor((remainingTime % (1000 * 60)) / 1000);
  239. const dStr = days < 10 ? "0" + days : days;
  240. const hStr = hours < 10 ? "0" + hours : hours;
  241. const mStr = minutes < 10 ? "0" + minutes : minutes;
  242. const sStr = seconds < 10 ? "0" + seconds : seconds;
  243. return `${dStr}d ${hStr}h:${mStr}m:${sStr}s`;
  244. }
  245. public static formatTimeRemaining(targetTimestamp) {
  246. // 获取当前时间戳
  247. const now = Date.now();
  248. // 计算剩余时间的毫秒数
  249. let remainingTime = targetTimestamp - now;
  250. // 如果剩余时间小于等于 0,表示目标时间已经过去
  251. if (remainingTime <= 0) {
  252. return "00:00:00";
  253. }
  254. // 计算小时、分钟、秒
  255. const hours = Math.floor(remainingTime / (1000 * 60 * 60));
  256. remainingTime -= hours * 1000 * 60 * 60;
  257. const minutes = Math.floor(remainingTime / (1000 * 60));
  258. remainingTime -= minutes * 1000 * 60;
  259. const seconds = Math.floor(remainingTime / 1000);
  260. // 格式化为两位数的字符串
  261. const formattedHours = Utils.padStartCustom(hours, 2, "0");
  262. const formattedMinutes = Utils.padStartCustom(minutes, 2, "0");
  263. const formattedSeconds = Utils.padStartCustom(seconds, 2, "0");
  264. return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
  265. }
  266. // 秒转换为时分秒
  267. public static getTimeStrByS(second: number) {
  268. second = Math.floor(second);
  269. if (second < 0) second = 0;
  270. var d = Math.floor(second / 3600 / 24);
  271. second -= d * 3600 * 24;
  272. var h = Math.floor(second / 3600);
  273. second -= h * 3600;
  274. var m = Math.floor(second / 60);
  275. second -= m * 60;
  276. var front = "00";
  277. if (h > 9) {
  278. front = "" + h;
  279. } else {
  280. front = "0" + h;
  281. }
  282. var mid = "00";
  283. if (m > 9) {
  284. mid = "" + m;
  285. } else {
  286. mid = "0" + m;
  287. }
  288. var back = "00";
  289. if (second > 9) {
  290. back = "" + second;
  291. } else {
  292. back = "0" + second;
  293. }
  294. if (d > 0) {
  295. return d + ":" + h + ":" + m;
  296. } else {
  297. var longTime = h > 0;
  298. if (longTime) {
  299. return front + ":" + mid;
  300. } else {
  301. return mid + ":" + back; //+ '秒';
  302. }
  303. }
  304. }
  305. public static formattedDate(timestamp) {
  306. const date = new Date(Number(timestamp));
  307. // 获取年月日
  308. const year = date.getFullYear();
  309. let month = date.getMonth() + 1;
  310. let monthS = month < 9 ? "0" + month : month;
  311. let day = date.getDate();
  312. let dayS = day < 9 ? "0" + day : day;
  313. const hours = date.getHours();
  314. let hoursS = hours < 9 ? "0" + hours : hours;
  315. const minutes = date.getMinutes();
  316. let minutesS = minutes < 9 ? "0" + minutes : minutes;
  317. const seconds = date.getSeconds();
  318. let secondsS = seconds < 9 ? "0" + seconds : seconds;
  319. const formattedDate = `${year}-${monthS}-${dayS} ${hoursS}:${minutesS}:${secondsS}`;
  320. return formattedDate;
  321. }
  322. public static formattedOnlyDate(timestamp) {
  323. const date = new Date(Number(timestamp));
  324. // 获取年月日
  325. const year = date.getFullYear();
  326. let month = date.getMonth() + 1;
  327. let monthS = month < 9 ? "0" + month : month;
  328. let day = date.getDate();
  329. let dayS = day < 9 ? "0" + day : day;
  330. const formattedDate = `${year}-${monthS}-${dayS}`;
  331. return formattedDate;
  332. }
  333. // 加载资源
  334. public static loadRes(path: string, type: typeof Asset): Promise<Asset> {
  335. return new Promise((resolve, reject) => {
  336. resources.load(path, type, (err, ret) => {
  337. if (err) {
  338. error(path, err);
  339. reject(null);
  340. } else {
  341. resolve(ret);
  342. }
  343. });
  344. });
  345. }
  346. public static loadRemote(path: string, type: typeof ImageAsset) {
  347. return new Promise((resolve, reject) => {
  348. assetManager.loadRemote(path, type, (err, ret) => {
  349. if (err) {
  350. error(path, err);
  351. reject(null);
  352. } else {
  353. resolve(ret);
  354. }
  355. });
  356. });
  357. }
  358. // 权重
  359. public static weight(v: number[]): number {
  360. var mTotalWeight = 0;
  361. for (var i = 0; i < v.length; ++i) {
  362. mTotalWeight += v[i];
  363. }
  364. if (mTotalWeight <= 0) return -1;
  365. var randnum = Math.round(Math.random() * Number.MAX_VALUE) % mTotalWeight;
  366. for (var i = 0; i < v.length; ++i) {
  367. if (randnum < v[i]) {
  368. return i;
  369. } else {
  370. randnum -= v[i];
  371. }
  372. }
  373. return -1;
  374. }
  375. //定点数
  376. public static fixFloat(val: number, count: number = 2) {
  377. var a = count * 100;
  378. return Math.floor(val * a) / a;
  379. }
  380. // 在子节点中查找
  381. private static _findInChildren(node: Node, name: string): Node {
  382. var x = node.getChildByName(name);
  383. if (x) return x;
  384. if (node.children.length == 0) return null;
  385. for (var i = 0; i < node.children.length; ++i) {
  386. var tmp = this._findInChildren(node.children[i], name);
  387. if (tmp) return tmp;
  388. }
  389. return null;
  390. }
  391. // 飞动动画
  392. public static flyAnim(
  393. type: number,
  394. startNode: Node,
  395. targetNodeName: string,
  396. count: number,
  397. radius: number,
  398. callback: Function
  399. ) {
  400. let srcNode = this._findInChildren(director.getScene(), targetNodeName);
  401. if (!srcNode) {
  402. notPlay = false;
  403. callback();
  404. return;
  405. }
  406. let getPoint = (r, ox, oy, count) => {
  407. var point = []; //结果
  408. var radians = (Math.PI / 180) * Math.round(360 / count), //弧度
  409. i = 0;
  410. for (; i < count; i++) {
  411. var x = ox + r * Math.sin(radians * i),
  412. y = oy + r * Math.cos(radians * i);
  413. point.unshift(v2(x, y)); //为保持数据顺时针
  414. }
  415. return point;
  416. };
  417. let createNode = (type) => {
  418. if (type == 0) return PoolMgr.Instance().get("Coin");
  419. if (type == 1) return PoolMgr.Instance().get("Gem");
  420. if (type == 2) return PoolMgr.Instance().get("Zp");
  421. if (type == 3) return PoolMgr.Instance().get("ZpGold");
  422. };
  423. let start = startNode.parent
  424. .getComponent(UITransform)
  425. .convertToWorldSpaceAR(startNode.position);
  426. start = find("Canvas/flyNode")
  427. .getComponent(UITransform)
  428. .convertToNodeSpaceAR(start);
  429. var array = getPoint(radius, start.x, start.y, count);
  430. var nodeArray = new Array();
  431. for (var i = 0; i < array.length; i++) {
  432. var gold = createNode(type);
  433. gold.parent = find("Canvas/flyNode");
  434. var randPos = v2(
  435. array[i].x + Utils.getRandomInt(0, 50),
  436. array[i].y + Utils.getRandomInt(0, 50)
  437. );
  438. gold.setPosition(start);
  439. nodeArray.push({ gold, randPos });
  440. }
  441. var notPlay = false;
  442. let dstPos = srcNode.parent
  443. .getComponent(UITransform)
  444. .convertToWorldSpaceAR(srcNode.position);
  445. dstPos = find("Canvas/flyNode")
  446. .getComponent(UITransform)
  447. .convertToNodeSpaceAR(dstPos);
  448. var targetGoldNode = srcNode;
  449. for (var i = 0; i < nodeArray.length; i++) {
  450. var pos = nodeArray[i].randPos;
  451. var node = nodeArray[i].gold;
  452. nodeArray[i].gold.id = i;
  453. tween(node)
  454. .to(0.2, { position: pos })
  455. .delay(i * 0.03)
  456. .to(0.5, { position: v2(dstPos.x, dstPos.y) })
  457. .call(() => {
  458. if (!notPlay) {
  459. targetGoldNode.scale = v3(1, 1, 1);
  460. notPlay = true;
  461. tween(targetGoldNode)
  462. .to(0.1, { scale: v3(2, 2, 2) })
  463. .to(0.1, { scale: v3(1, 1, 1) })
  464. .call(() => {
  465. notPlay = false;
  466. });
  467. }
  468. callback(node._id == nodeArray.length - 1);
  469. PoolMgr.Instance().put(node.name, node);
  470. if (node._id == nodeArray.length - 1) {
  471. find("Canvas/flyNode").removeAllChildren();
  472. }
  473. })
  474. .start();
  475. }
  476. }
  477. /**
  478. * 深度复制
  479. * @param value
  480. */
  481. public static deepClone(value: any) {
  482. // 处理基本数据类型和特殊情况
  483. if (value === null || typeof value !== "object") {
  484. return value; // 基本数据类型和 null
  485. }
  486. // 处理数组
  487. if (Array.isArray(value)) {
  488. return value.map((item) => Utils.deepClone(item)); // 递归拷贝每个元素
  489. }
  490. // 处理对象
  491. const copy = {};
  492. for (const key in value) {
  493. if (value.hasOwnProperty(key)) {
  494. copy[key] = Utils.deepClone(value[key]); // 递归拷贝每个属性
  495. }
  496. }
  497. return copy;
  498. }
  499. /**
  500. * 获取url参数
  501. * @param name
  502. * @returns
  503. */
  504. public static getQueryString(name: any) {
  505. var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
  506. var r = window.location.search.substr(1).match(reg);
  507. if (r != null) return unescape(r[2]);
  508. return null;
  509. }
  510. /**
  511. * 贝塞尔曲线计算
  512. * @param points
  513. * @param t
  514. */
  515. public static calculateBezier(points: Vec2[], t: number) {
  516. const n = points.length;
  517. if (n === 0) return v2(0, 0); // 如果没有控制点,返回 (0,0)
  518. if (n === 1) return points[0]; // 如果只有一个控制点,返回该点
  519. let tempPoints = points.slice(); // 创建控制点的副本
  520. // De Casteljau's algorithm
  521. for (let r = 1; r < n; r++) {
  522. for (let i = 0; i < n - r; i++) {
  523. tempPoints[i] = v2(
  524. (1 - t) * tempPoints[i].x + t * tempPoints[i + 1].x,
  525. (1 - t) * tempPoints[i].y + t * tempPoints[i + 1].y
  526. );
  527. }
  528. }
  529. return tempPoints[0]; // 返回计算得到的点
  530. }
  531. /**
  532. * 是否为某个链接
  533. * @param url
  534. * @param hostname
  535. * @returns
  536. */
  537. static isNameLink(url: string | URL, hostname: string) {
  538. try {
  539. const parsedUrl = new URL(url);
  540. return parsedUrl.hostname === hostname;
  541. } catch (e) {
  542. console.error("Invalid URL");
  543. return false;
  544. }
  545. }
  546. // 将用户 ID 转换为 26 进制字符串
  547. public static convertTo26(userId) {
  548. let sb = "";
  549. while (userId > 0) {
  550. userId--;
  551. let ch = String.fromCharCode((userId % 26) + "A".charCodeAt(0));
  552. sb = ch + sb;
  553. userId = Math.floor(userId / 26);
  554. }
  555. return sb;
  556. }
  557. // 生成邀请代码
  558. public static generateInviteCode(channelId, userId) {
  559. return channelId + this.convertTo26(userId);
  560. }
  561. // 取后缀
  562. public static getSuffix(idx) {
  563. idx = Math.floor(idx);
  564. if (idx <= 0) {
  565. return "";
  566. }
  567. idx = idx - 1;
  568. let result = "";
  569. while (idx >= 0) {
  570. result = String.fromCharCode((idx % 26) + 97) + result;
  571. idx = Math.floor(idx / 26) - 1;
  572. }
  573. result = "";
  574. return result;
  575. }
  576. // max: 10 ==> 1->1,2->2,..10->10,11->1
  577. public static getMappedValue(idx, max) {
  578. let result;
  579. result = idx % max == 0 ? max : idx % max;
  580. return result;
  581. }
  582. //
  583. public static getStarValue(idx, max) {
  584. let result = Math.floor((idx - 1) / max);
  585. return result;
  586. }
  587. // 格式化地址
  588. static formAddress(address: string) {
  589. if (
  590. address == null ||
  591. address == "" ||
  592. address == undefined ||
  593. address.length < 10
  594. ) {
  595. return " ";
  596. }
  597. return (
  598. address.substring(0, 8) +
  599. "..." +
  600. address.substring(address.length - 8, address.length)
  601. );
  602. }
  603. //
  604. public static updateLabelText(fullText, maxWidth, fontSize) {
  605. let str = "";
  606. // 计算文本的实际宽度
  607. let textWidth = this.getTextWidth(fullText, fontSize);
  608. if (textWidth > maxWidth) {
  609. // 超过最大宽度,显示截断后的文本并加上省略号
  610. let truncatedText = this.truncateText(fullText, maxWidth, fontSize);
  611. str = truncatedText + "...";
  612. } else {
  613. // 显示完整文本
  614. str = fullText;
  615. }
  616. return str;
  617. }
  618. // 获取文本的宽度
  619. private static getTextWidth(text, fontSize) {
  620. let canvas = document.createElement("canvas");
  621. let ctx = canvas.getContext("2d");
  622. ctx.font = fontSize + "px Arial"; // 设置字体大小
  623. return ctx.measureText(text).width;
  624. }
  625. // 截断文本并返回适合的文本
  626. private static truncateText(text, maxWidth, fontSize) {
  627. let i = 0;
  628. let truncatedText = text;
  629. // 循环截取字符直到文本超出最大宽度
  630. while (
  631. this.getTextWidth(truncatedText, fontSize) > maxWidth &&
  632. i < text.length
  633. ) {
  634. i++;
  635. truncatedText = text.slice(0, text.length - i);
  636. }
  637. return truncatedText;
  638. }
  639. // 多语言
  640. static setI18nLabel(str: string, value?: string, value2?: string) {
  641. let i18nStr = str;
  642. i18nStr = LanguageManager.getText(str);
  643. if (value) {
  644. i18nStr = i18nStr.replace("{value}", value);
  645. }
  646. if (value2) {
  647. i18nStr = i18nStr.replace("{value2}", value2);
  648. }
  649. return i18nStr;
  650. }
  651. }