react-util-tools 1.0.26 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,7 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- Decimal: () => import_decimal.default,
33
+ Decimal: () => import_decimal2.default,
34
34
  XLSX: () => XLSX,
35
35
  abs: () => abs,
36
36
  add: () => add,
@@ -464,6 +464,7 @@ function getDeviceInfo() {
464
464
  }
465
465
 
466
466
  // src/format/index.ts
467
+ var import_decimal = __toESM(require("decimal.js"), 1);
467
468
  function formatMoney(amount, options = {}) {
468
469
  const {
469
470
  decimals = 2,
@@ -471,20 +472,19 @@ function formatMoney(amount, options = {}) {
471
472
  separator = ",",
472
473
  decimalPoint = "."
473
474
  } = options;
474
- const num = typeof amount === "string" ? parseFloat(amount) : amount;
475
- if (isNaN(num)) {
476
- return `${symbol}0${decimalPoint}${"0".repeat(decimals)}`;
477
- }
478
- const isNegative = num < 0;
479
- const absNum = Math.abs(num);
480
- const fixed = absNum.toFixed(decimals);
481
- const [integerPart, decimalPart] = fixed.split(".");
482
- const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
483
- let result = symbol + formattedInteger;
484
- if (decimals > 0 && decimalPart) {
485
- result += decimalPoint + decimalPart;
486
- }
487
- return isNegative ? `-${result}` : result;
475
+ return tryRun(() => {
476
+ const dec = new import_decimal.default(amount);
477
+ const isNegative = dec.isNegative();
478
+ const absDec = dec.abs();
479
+ const fixed = absDec.toFixed(decimals, import_decimal.default.ROUND_DOWN);
480
+ const [integerPart, decimalPart] = fixed.split(".");
481
+ const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
482
+ let result = symbol + formattedInteger;
483
+ if (decimals > 0 && decimalPart) {
484
+ result += decimalPoint + decimalPart;
485
+ }
486
+ return isNegative ? `-${result}` : result;
487
+ }) ?? `${symbol}0${decimalPoint}${"0".repeat(decimals)}`;
488
488
  }
489
489
  function parseMoney(formattedAmount) {
490
490
  if (!formattedAmount || typeof formattedAmount !== "string") {
@@ -495,68 +495,70 @@ function parseMoney(formattedAmount) {
495
495
  return isNaN(num) ? 0 : num;
496
496
  }
497
497
  function formatNumber(amount, decimals = 2) {
498
- const num = typeof amount === "string" ? parseFloat(amount) : amount;
499
- if (isNaN(num)) {
500
- return "0." + "0".repeat(decimals);
501
- }
502
- const fixed = num.toFixed(decimals);
503
- const [integerPart, decimalPart] = fixed.split(".");
504
- const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
505
- return decimals > 0 && decimalPart ? `${formattedInteger}.${decimalPart}` : formattedInteger;
498
+ return tryRun(() => {
499
+ const dec = new import_decimal.default(amount);
500
+ const fixed = dec.toFixed(decimals, import_decimal.default.ROUND_DOWN);
501
+ const [integerPart, decimalPart] = fixed.split(".");
502
+ const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
503
+ return decimals > 0 && decimalPart ? `${formattedInteger}.${decimalPart}` : formattedInteger;
504
+ }) ?? "0." + "0".repeat(decimals);
506
505
  }
507
506
  function formatMoneyToChinese(amount) {
508
- const num = typeof amount === "string" ? parseFloat(amount) : amount;
509
- if (isNaN(num) || num < 0) {
510
- return "\u96F6\u5143\u6574";
511
- }
512
- const digits = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
513
- const units = ["", "\u62FE", "\u4F70", "\u4EDF"];
514
- const bigUnits = ["", "\u4E07", "\u4EBF", "\u5146"];
515
- const decimalUnits = ["\u89D2", "\u5206"];
516
- const [integerPart, decimalPart] = num.toFixed(2).split(".");
517
- let result = "";
518
- if (integerPart === "0") {
519
- result = "\u96F6\u5143";
520
- } else {
521
- const integerStr = integerPart;
522
- const len = integerStr.length;
523
- let zeroCount = 0;
524
- for (let i = 0; i < len; i++) {
525
- const digit = parseInt(integerStr[i]);
526
- const unitIndex = (len - i - 1) % 4;
527
- const bigUnitIndex = Math.floor((len - i - 1) / 4);
528
- if (digit === 0) {
529
- zeroCount++;
530
- } else {
531
- if (zeroCount > 0) {
532
- result += "\u96F6";
507
+ return tryRun(() => {
508
+ const dec = new import_decimal.default(amount);
509
+ if (dec.isNegative()) {
510
+ return "\u96F6\u5143\u6574";
511
+ }
512
+ const num = dec.toNumber();
513
+ const digits = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
514
+ const units = ["", "\u62FE", "\u4F70", "\u4EDF"];
515
+ const bigUnits = ["", "\u4E07", "\u4EBF", "\u5146"];
516
+ const decimalUnits = ["\u89D2", "\u5206"];
517
+ const [integerPart, decimalPart] = num.toFixed(2).split(".");
518
+ let result = "";
519
+ if (integerPart === "0") {
520
+ result = "\u96F6\u5143";
521
+ } else {
522
+ const integerStr = integerPart;
523
+ const len = integerStr.length;
524
+ let zeroCount = 0;
525
+ for (let i = 0; i < len; i++) {
526
+ const digit = parseInt(integerStr[i]);
527
+ const unitIndex = (len - i - 1) % 4;
528
+ const bigUnitIndex = Math.floor((len - i - 1) / 4);
529
+ if (digit === 0) {
530
+ zeroCount++;
531
+ } else {
532
+ if (zeroCount > 0) {
533
+ result += "\u96F6";
534
+ }
535
+ result += digits[digit] + units[unitIndex];
536
+ zeroCount = 0;
533
537
  }
534
- result += digits[digit] + units[unitIndex];
535
- zeroCount = 0;
536
- }
537
- if (unitIndex === 0 && bigUnitIndex > 0) {
538
- if (result[result.length - 1] !== bigUnits[bigUnitIndex]) {
539
- result += bigUnits[bigUnitIndex];
538
+ if (unitIndex === 0 && bigUnitIndex > 0) {
539
+ if (result[result.length - 1] !== bigUnits[bigUnitIndex]) {
540
+ result += bigUnits[bigUnitIndex];
541
+ }
540
542
  }
541
543
  }
544
+ result += "\u5143";
542
545
  }
543
- result += "\u5143";
544
- }
545
- if (decimalPart && decimalPart !== "00") {
546
- const jiao = parseInt(decimalPart[0]);
547
- const fen = parseInt(decimalPart[1]);
548
- if (jiao > 0) {
549
- result += digits[jiao] + decimalUnits[0];
550
- } else if (fen > 0) {
551
- result += "\u96F6";
552
- }
553
- if (fen > 0) {
554
- result += digits[fen] + decimalUnits[1];
546
+ if (decimalPart && decimalPart !== "00") {
547
+ const jiao = parseInt(decimalPart[0]);
548
+ const fen = parseInt(decimalPart[1]);
549
+ if (jiao > 0) {
550
+ result += digits[jiao] + decimalUnits[0];
551
+ } else if (fen > 0) {
552
+ result += "\u96F6";
553
+ }
554
+ if (fen > 0) {
555
+ result += digits[fen] + decimalUnits[1];
556
+ }
557
+ } else {
558
+ result += "\u6574";
555
559
  }
556
- } else {
557
- result += "\u6574";
558
- }
559
- return result;
560
+ return result;
561
+ }) ?? "\u96F6\u5143\u6574";
560
562
  }
561
563
  function formatPercent(value, options = {}) {
562
564
  const { decimals = 2, multiply: multiply2 = true } = options;
@@ -599,6 +601,13 @@ function unmaskEmail(maskedEmail, originalEmail) {
599
601
  }
600
602
  return maskedEmail;
601
603
  }
604
+ function tryRun(fn) {
605
+ try {
606
+ return fn();
607
+ } catch (error) {
608
+ return null;
609
+ }
610
+ }
602
611
 
603
612
  // src/date/index.ts
604
613
  var import_date_fns = require("date-fns");
@@ -862,22 +871,22 @@ function getUTCWeekNumber(date) {
862
871
  }
863
872
 
864
873
  // src/decimal/index.ts
865
- var import_decimal = __toESM(require("decimal.js"), 1);
874
+ var import_decimal2 = __toESM(require("decimal.js"), 1);
866
875
 
867
876
  // src/decimal/utils/index.ts
868
- var import_decimal2 = __toESM(require("decimal.js"), 1);
877
+ var import_decimal3 = __toESM(require("decimal.js"), 1);
869
878
  function safeDecimal(value) {
870
879
  try {
871
- if (value instanceof import_decimal2.default) {
880
+ if (value instanceof import_decimal3.default) {
872
881
  return value;
873
882
  }
874
- const decimal = new import_decimal2.default(value);
883
+ const decimal = new import_decimal3.default(value);
875
884
  if (decimal.isNaN()) {
876
- return new import_decimal2.default(0);
885
+ return new import_decimal3.default(0);
877
886
  }
878
887
  return decimal;
879
888
  } catch {
880
- return new import_decimal2.default(0);
889
+ return new import_decimal3.default(0);
881
890
  }
882
891
  }
883
892
  function add(a, b) {
@@ -975,7 +984,7 @@ function round(value, decimalPlaces = 2) {
975
984
  function ceil(value, decimalPlaces = 2) {
976
985
  try {
977
986
  const decimal = safeDecimal(value);
978
- return decimal.toDecimalPlaces(decimalPlaces, import_decimal2.default.ROUND_CEIL).toNumber();
987
+ return decimal.toDecimalPlaces(decimalPlaces, import_decimal3.default.ROUND_CEIL).toNumber();
979
988
  } catch {
980
989
  return 0;
981
990
  }
@@ -983,7 +992,7 @@ function ceil(value, decimalPlaces = 2) {
983
992
  function floor(value, decimalPlaces = 2) {
984
993
  try {
985
994
  const decimal = safeDecimal(value);
986
- return decimal.toDecimalPlaces(decimalPlaces, import_decimal2.default.ROUND_FLOOR).toNumber();
995
+ return decimal.toDecimalPlaces(decimalPlaces, import_decimal3.default.ROUND_FLOOR).toNumber();
987
996
  } catch {
988
997
  return 0;
989
998
  }
package/dist/index.js CHANGED
@@ -260,6 +260,7 @@ function getDeviceInfo() {
260
260
  }
261
261
 
262
262
  // src/format/index.ts
263
+ import Decimal from "decimal.js";
263
264
  function formatMoney(amount, options = {}) {
264
265
  const {
265
266
  decimals = 2,
@@ -267,20 +268,19 @@ function formatMoney(amount, options = {}) {
267
268
  separator = ",",
268
269
  decimalPoint = "."
269
270
  } = options;
270
- const num = typeof amount === "string" ? parseFloat(amount) : amount;
271
- if (isNaN(num)) {
272
- return `${symbol}0${decimalPoint}${"0".repeat(decimals)}`;
273
- }
274
- const isNegative = num < 0;
275
- const absNum = Math.abs(num);
276
- const fixed = absNum.toFixed(decimals);
277
- const [integerPart, decimalPart] = fixed.split(".");
278
- const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
279
- let result = symbol + formattedInteger;
280
- if (decimals > 0 && decimalPart) {
281
- result += decimalPoint + decimalPart;
282
- }
283
- return isNegative ? `-${result}` : result;
271
+ return tryRun(() => {
272
+ const dec = new Decimal(amount);
273
+ const isNegative = dec.isNegative();
274
+ const absDec = dec.abs();
275
+ const fixed = absDec.toFixed(decimals, Decimal.ROUND_DOWN);
276
+ const [integerPart, decimalPart] = fixed.split(".");
277
+ const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
278
+ let result = symbol + formattedInteger;
279
+ if (decimals > 0 && decimalPart) {
280
+ result += decimalPoint + decimalPart;
281
+ }
282
+ return isNegative ? `-${result}` : result;
283
+ }) ?? `${symbol}0${decimalPoint}${"0".repeat(decimals)}`;
284
284
  }
285
285
  function parseMoney(formattedAmount) {
286
286
  if (!formattedAmount || typeof formattedAmount !== "string") {
@@ -291,68 +291,70 @@ function parseMoney(formattedAmount) {
291
291
  return isNaN(num) ? 0 : num;
292
292
  }
293
293
  function formatNumber(amount, decimals = 2) {
294
- const num = typeof amount === "string" ? parseFloat(amount) : amount;
295
- if (isNaN(num)) {
296
- return "0." + "0".repeat(decimals);
297
- }
298
- const fixed = num.toFixed(decimals);
299
- const [integerPart, decimalPart] = fixed.split(".");
300
- const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
301
- return decimals > 0 && decimalPart ? `${formattedInteger}.${decimalPart}` : formattedInteger;
294
+ return tryRun(() => {
295
+ const dec = new Decimal(amount);
296
+ const fixed = dec.toFixed(decimals, Decimal.ROUND_DOWN);
297
+ const [integerPart, decimalPart] = fixed.split(".");
298
+ const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
299
+ return decimals > 0 && decimalPart ? `${formattedInteger}.${decimalPart}` : formattedInteger;
300
+ }) ?? "0." + "0".repeat(decimals);
302
301
  }
303
302
  function formatMoneyToChinese(amount) {
304
- const num = typeof amount === "string" ? parseFloat(amount) : amount;
305
- if (isNaN(num) || num < 0) {
306
- return "\u96F6\u5143\u6574";
307
- }
308
- const digits = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
309
- const units = ["", "\u62FE", "\u4F70", "\u4EDF"];
310
- const bigUnits = ["", "\u4E07", "\u4EBF", "\u5146"];
311
- const decimalUnits = ["\u89D2", "\u5206"];
312
- const [integerPart, decimalPart] = num.toFixed(2).split(".");
313
- let result = "";
314
- if (integerPart === "0") {
315
- result = "\u96F6\u5143";
316
- } else {
317
- const integerStr = integerPart;
318
- const len = integerStr.length;
319
- let zeroCount = 0;
320
- for (let i = 0; i < len; i++) {
321
- const digit = parseInt(integerStr[i]);
322
- const unitIndex = (len - i - 1) % 4;
323
- const bigUnitIndex = Math.floor((len - i - 1) / 4);
324
- if (digit === 0) {
325
- zeroCount++;
326
- } else {
327
- if (zeroCount > 0) {
328
- result += "\u96F6";
303
+ return tryRun(() => {
304
+ const dec = new Decimal(amount);
305
+ if (dec.isNegative()) {
306
+ return "\u96F6\u5143\u6574";
307
+ }
308
+ const num = dec.toNumber();
309
+ const digits = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
310
+ const units = ["", "\u62FE", "\u4F70", "\u4EDF"];
311
+ const bigUnits = ["", "\u4E07", "\u4EBF", "\u5146"];
312
+ const decimalUnits = ["\u89D2", "\u5206"];
313
+ const [integerPart, decimalPart] = num.toFixed(2).split(".");
314
+ let result = "";
315
+ if (integerPart === "0") {
316
+ result = "\u96F6\u5143";
317
+ } else {
318
+ const integerStr = integerPart;
319
+ const len = integerStr.length;
320
+ let zeroCount = 0;
321
+ for (let i = 0; i < len; i++) {
322
+ const digit = parseInt(integerStr[i]);
323
+ const unitIndex = (len - i - 1) % 4;
324
+ const bigUnitIndex = Math.floor((len - i - 1) / 4);
325
+ if (digit === 0) {
326
+ zeroCount++;
327
+ } else {
328
+ if (zeroCount > 0) {
329
+ result += "\u96F6";
330
+ }
331
+ result += digits[digit] + units[unitIndex];
332
+ zeroCount = 0;
329
333
  }
330
- result += digits[digit] + units[unitIndex];
331
- zeroCount = 0;
332
- }
333
- if (unitIndex === 0 && bigUnitIndex > 0) {
334
- if (result[result.length - 1] !== bigUnits[bigUnitIndex]) {
335
- result += bigUnits[bigUnitIndex];
334
+ if (unitIndex === 0 && bigUnitIndex > 0) {
335
+ if (result[result.length - 1] !== bigUnits[bigUnitIndex]) {
336
+ result += bigUnits[bigUnitIndex];
337
+ }
336
338
  }
337
339
  }
340
+ result += "\u5143";
338
341
  }
339
- result += "\u5143";
340
- }
341
- if (decimalPart && decimalPart !== "00") {
342
- const jiao = parseInt(decimalPart[0]);
343
- const fen = parseInt(decimalPart[1]);
344
- if (jiao > 0) {
345
- result += digits[jiao] + decimalUnits[0];
346
- } else if (fen > 0) {
347
- result += "\u96F6";
348
- }
349
- if (fen > 0) {
350
- result += digits[fen] + decimalUnits[1];
342
+ if (decimalPart && decimalPart !== "00") {
343
+ const jiao = parseInt(decimalPart[0]);
344
+ const fen = parseInt(decimalPart[1]);
345
+ if (jiao > 0) {
346
+ result += digits[jiao] + decimalUnits[0];
347
+ } else if (fen > 0) {
348
+ result += "\u96F6";
349
+ }
350
+ if (fen > 0) {
351
+ result += digits[fen] + decimalUnits[1];
352
+ }
353
+ } else {
354
+ result += "\u6574";
351
355
  }
352
- } else {
353
- result += "\u6574";
354
- }
355
- return result;
356
+ return result;
357
+ }) ?? "\u96F6\u5143\u6574";
356
358
  }
357
359
  function formatPercent(value, options = {}) {
358
360
  const { decimals = 2, multiply: multiply2 = true } = options;
@@ -395,6 +397,13 @@ function unmaskEmail(maskedEmail, originalEmail) {
395
397
  }
396
398
  return maskedEmail;
397
399
  }
400
+ function tryRun(fn) {
401
+ try {
402
+ return fn();
403
+ } catch (error) {
404
+ return null;
405
+ }
406
+ }
398
407
 
399
408
  // src/date/index.ts
400
409
  import {
@@ -704,19 +713,19 @@ function getUTCWeekNumber(date) {
704
713
  import { default as default2 } from "decimal.js";
705
714
 
706
715
  // src/decimal/utils/index.ts
707
- import Decimal from "decimal.js";
716
+ import Decimal2 from "decimal.js";
708
717
  function safeDecimal(value) {
709
718
  try {
710
- if (value instanceof Decimal) {
719
+ if (value instanceof Decimal2) {
711
720
  return value;
712
721
  }
713
- const decimal = new Decimal(value);
722
+ const decimal = new Decimal2(value);
714
723
  if (decimal.isNaN()) {
715
- return new Decimal(0);
724
+ return new Decimal2(0);
716
725
  }
717
726
  return decimal;
718
727
  } catch {
719
- return new Decimal(0);
728
+ return new Decimal2(0);
720
729
  }
721
730
  }
722
731
  function add(a, b) {
@@ -814,7 +823,7 @@ function round(value, decimalPlaces = 2) {
814
823
  function ceil(value, decimalPlaces = 2) {
815
824
  try {
816
825
  const decimal = safeDecimal(value);
817
- return decimal.toDecimalPlaces(decimalPlaces, Decimal.ROUND_CEIL).toNumber();
826
+ return decimal.toDecimalPlaces(decimalPlaces, Decimal2.ROUND_CEIL).toNumber();
818
827
  } catch {
819
828
  return 0;
820
829
  }
@@ -822,7 +831,7 @@ function ceil(value, decimalPlaces = 2) {
822
831
  function floor(value, decimalPlaces = 2) {
823
832
  try {
824
833
  const decimal = safeDecimal(value);
825
- return decimal.toDecimalPlaces(decimalPlaces, Decimal.ROUND_FLOOR).toNumber();
834
+ return decimal.toDecimalPlaces(decimalPlaces, Decimal2.ROUND_FLOOR).toNumber();
826
835
  } catch {
827
836
  return 0;
828
837
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-util-tools",
3
- "version": "1.0.26",
3
+ "version": "1.0.27",
4
4
  "description": "A collection of useful utilities: throttle, debounce, date formatting, device detection, money formatting, decimal calculations, Excel processing and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -1,3 +1,5 @@
1
+ import Decimal from "decimal.js"
2
+
1
3
  /**
2
4
  * 金额格式化:将数字格式化为金额字符串
3
5
  * @param amount 金额数字
@@ -20,32 +22,29 @@ export function formatMoney(
20
22
  decimalPoint = '.'
21
23
  } = options
22
24
 
23
- // 转换为数字
24
- const num = typeof amount === 'string' ? parseFloat(amount) : amount
25
-
26
- // 处理无效数字
27
- if (isNaN(num)) {
28
- return `${symbol}0${decimalPoint}${'0'.repeat(decimals)}`
29
- }
30
-
31
- // 处理负数
32
- const isNegative = num < 0
33
- const absNum = Math.abs(num)
34
-
35
- // 固定小数位
36
- const fixed = absNum.toFixed(decimals)
37
- const [integerPart, decimalPart] = fixed.split('.')
38
-
39
- // 添加千分位分隔符
40
- const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator)
41
-
42
- // 组合结果
43
- let result = symbol + formattedInteger
44
- if (decimals > 0 && decimalPart) {
45
- result += decimalPoint + decimalPart
46
- }
47
-
48
- return isNegative ? `-${result}` : result
25
+ return tryRun(() => {
26
+ // 使用 Decimal 处理,避免精度问题
27
+ const dec = new Decimal(amount)
28
+
29
+ // 处理负数
30
+ const isNegative = dec.isNegative()
31
+ const absDec = dec.abs()
32
+
33
+ // 固定小数位(向下取整)
34
+ const fixed = absDec.toFixed(decimals, Decimal.ROUND_DOWN)
35
+ const [integerPart, decimalPart] = fixed.split('.')
36
+
37
+ // 添加千分位分隔符
38
+ const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator)
39
+
40
+ // 组合结果
41
+ let result = symbol + formattedInteger
42
+ if (decimals > 0 && decimalPart) {
43
+ result += decimalPoint + decimalPart
44
+ }
45
+
46
+ return isNegative ? `-${result}` : result
47
+ }) ?? `${symbol}0${decimalPoint}${'0'.repeat(decimals)}`
49
48
  }
50
49
 
51
50
  /**
@@ -74,20 +73,18 @@ export function parseMoney(formattedAmount: string): number {
74
73
  * @returns 格式化后的金额字符串(不含货币符号)
75
74
  */
76
75
  export function formatNumber(amount: number | string, decimals = 2): string {
77
- const num = typeof amount === 'string' ? parseFloat(amount) : amount
78
-
79
- if (isNaN(num)) {
80
- return '0.' + '0'.repeat(decimals)
81
- }
82
-
83
- const fixed = num.toFixed(decimals)
84
- const [integerPart, decimalPart] = fixed.split('.')
85
-
86
- const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
87
-
88
- return decimals > 0 && decimalPart
89
- ? `${formattedInteger}.${decimalPart}`
90
- : formattedInteger
76
+ return tryRun(() => {
77
+ // 使用 Decimal 处理,避免精度问题
78
+ const dec = new Decimal(amount)
79
+ const fixed = dec.toFixed(decimals, Decimal.ROUND_DOWN)
80
+ const [integerPart, decimalPart] = fixed.split('.')
81
+
82
+ const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
83
+
84
+ return decimals > 0 && decimalPart
85
+ ? `${formattedInteger}.${decimalPart}`
86
+ : formattedInteger
87
+ }) ?? '0.' + '0'.repeat(decimals)
91
88
  }
92
89
 
93
90
  /**
@@ -96,73 +93,76 @@ export function formatNumber(amount: number | string, decimals = 2): string {
96
93
  * @returns 中文大写金额
97
94
  */
98
95
  export function formatMoneyToChinese(amount: number | string): string {
99
- const num = typeof amount === 'string' ? parseFloat(amount) : amount
100
-
101
- if (isNaN(num) || num < 0) {
102
- return '零元整'
103
- }
104
-
105
- const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
106
- const units = ['', '拾', '佰', '仟']
107
- const bigUnits = ['', '', '亿', '']
108
- const decimalUnits = ['', '']
109
-
110
- // 分离整数和小数部分
111
- const [integerPart, decimalPart] = num.toFixed(2).split('.')
112
- let result = ''
113
-
114
- // 处理整数部分
115
- if (integerPart === '0') {
116
- result = '零元'
117
- } else {
118
- const integerStr = integerPart
119
- const len = integerStr.length
120
- let zeroCount = 0
121
-
122
- for (let i = 0; i < len; i++) {
123
- const digit = parseInt(integerStr[i])
124
- const unitIndex = (len - i - 1) % 4
125
- const bigUnitIndex = Math.floor((len - i - 1) / 4)
126
-
127
- if (digit === 0) {
128
- zeroCount++
129
- } else {
130
- if (zeroCount > 0) {
131
- result += '零'
96
+ return tryRun(() => {
97
+ const dec = new Decimal(amount)
98
+
99
+ if (dec.isNegative()) {
100
+ return '零元整'
101
+ }
102
+
103
+ const num = dec.toNumber()
104
+ const digits = ['', '', '', '', '肆', '伍', '陆', '柒', '捌', '玖']
105
+ const units = ['', '', '佰', '仟']
106
+ const bigUnits = ['', '万', '亿', '兆']
107
+ const decimalUnits = ['角', '分']
108
+
109
+ // 分离整数和小数部分
110
+ const [integerPart, decimalPart] = num.toFixed(2).split('.')
111
+ let result = ''
112
+
113
+ // 处理整数部分
114
+ if (integerPart === '0') {
115
+ result = '零元'
116
+ } else {
117
+ const integerStr = integerPart
118
+ const len = integerStr.length
119
+ let zeroCount = 0
120
+
121
+ for (let i = 0; i < len; i++) {
122
+ const digit = parseInt(integerStr[i])
123
+ const unitIndex = (len - i - 1) % 4
124
+ const bigUnitIndex = Math.floor((len - i - 1) / 4)
125
+
126
+ if (digit === 0) {
127
+ zeroCount++
128
+ } else {
129
+ if (zeroCount > 0) {
130
+ result += '零'
131
+ }
132
+ result += digits[digit] + units[unitIndex]
133
+ zeroCount = 0
132
134
  }
133
- result += digits[digit] + units[unitIndex]
134
- zeroCount = 0
135
- }
136
-
137
- if (unitIndex === 0 && bigUnitIndex > 0) {
138
- if (result[result.length - 1] !== bigUnits[bigUnitIndex]) {
139
- result += bigUnits[bigUnitIndex]
135
+
136
+ if (unitIndex === 0 && bigUnitIndex > 0) {
137
+ if (result[result.length - 1] !== bigUnits[bigUnitIndex]) {
138
+ result += bigUnits[bigUnitIndex]
139
+ }
140
140
  }
141
141
  }
142
+
143
+ result += '元'
142
144
  }
143
-
144
- result += '元'
145
- }
146
-
147
- // 处理小数部分
148
- if (decimalPart && decimalPart !== '00') {
149
- const jiao = parseInt(decimalPart[0])
150
- const fen = parseInt(decimalPart[1])
151
-
152
- if (jiao > 0) {
153
- result += digits[jiao] + decimalUnits[0]
154
- } else if (fen > 0) {
155
- result += '零'
156
- }
157
-
158
- if (fen > 0) {
159
- result += digits[fen] + decimalUnits[1]
145
+
146
+ // 处理小数部分
147
+ if (decimalPart && decimalPart !== '00') {
148
+ const jiao = parseInt(decimalPart[0])
149
+ const fen = parseInt(decimalPart[1])
150
+
151
+ if (jiao > 0) {
152
+ result += digits[jiao] + decimalUnits[0]
153
+ } else if (fen > 0) {
154
+ result += '零'
155
+ }
156
+
157
+ if (fen > 0) {
158
+ result += digits[fen] + decimalUnits[1]
159
+ }
160
+ } else {
161
+ result += '整'
160
162
  }
161
- } else {
162
- result += '整'
163
- }
164
-
165
- return result
163
+
164
+ return result
165
+ }) ?? '零元整'
166
166
  }
167
167
 
168
168
  /**
@@ -252,3 +252,164 @@ export function unmaskEmail(maskedEmail: string, originalEmail: string): string
252
252
 
253
253
  return maskedEmail // 不匹配,返回脱敏邮箱
254
254
  }
255
+
256
+ /* thousandths processing
257
+ ** value The coin string to be processed
258
+ */
259
+ export function toLocalString(value: string) {
260
+ // Do thousandths
261
+ let result = value
262
+ if (Number(value) >= 1000) {
263
+ result = Number(value).toLocaleString('en-US')
264
+ } else {
265
+ result = value
266
+ }
267
+ return result
268
+ }
269
+
270
+ export const integerTokenArr = ['SATS']
271
+ export type AmountNum = string | number | Decimal
272
+
273
+ export function removeInvalidZero(num: string) {
274
+ let result = num
275
+ if (num.includes('.')) {
276
+ result = result.replace(/\.?0+$/, '')
277
+ }
278
+ return result
279
+ }
280
+
281
+ //(<= 8)Output precision control, if it is greater than 8 digits, 8 digits will be reserved, if it is less than 8 digits, the corresponding digits will be displayed, and the default is 8 digits
282
+ export function formatePrecision(
283
+ n: AmountNum,
284
+ precision?: number,
285
+ tokenSymbol?: string
286
+ ) {
287
+ if (!isNaN(Number(n))) {
288
+ let prec = precision && precision <= 8 ? precision : 8
289
+ if (tokenSymbol && integerTokenArr.includes(tokenSymbol)) {
290
+ prec = 0
291
+ }
292
+ return new Decimal(Number(n)).toFixed(prec, Decimal.ROUND_DOWN)
293
+ }
294
+ const num = new Decimal(n).toNumber()
295
+ return num
296
+ }
297
+
298
+ /**
299
+ * (>= 8) formatted amount, number of tokens
300
+ * Keep 8 digits after the decimal point, a total of 10 digits
301
+ */
302
+ export function formateAmount({
303
+ num = 0,
304
+ precision = 8,
305
+ tokenSymbol = '',
306
+ type = '0',
307
+ }: {
308
+ num: AmountNum
309
+ precision?: number
310
+ tokenSymbol?: string
311
+ type?: string
312
+ }) {
313
+ return (
314
+ tryRun(() => {
315
+ const dec = new Decimal(num)
316
+ const { length } = dec.abs().floor().toString()
317
+ const pres = precision
318
+ precision = precision > 8 ? 9 : precision + 1
319
+ if (length >= precision) {
320
+ let value = ''
321
+ // Here it is necessary to judge whether it is greater than 1, and if it is greater than 1, directly retain 8 decimal places
322
+ if (dec.greaterThan(1)) {
323
+ value = dec.toFixed(8, Decimal.ROUND_DOWN)
324
+ } else {
325
+ value = dec.toFixed(precision - length, Decimal.ROUND_DOWN)
326
+ }
327
+ value = formatePrecision(value, pres).toString()
328
+ // If you round up here, the decimal place will be removed
329
+ // If it is a sats token, round up
330
+ if (integerTokenArr.includes(tokenSymbol)) {
331
+ value = new Decimal(value).toFixed(0, Decimal.ROUND_CEIL)
332
+ }
333
+ // Do thousandths
334
+ let result = ''
335
+ if (typeof value === 'string') {
336
+ if (value.includes('.')) {
337
+ result = value.replace(/\d(?=(\d{3})+\.)/g, '$&,')
338
+ } else {
339
+ result = toLocalString(value)
340
+ }
341
+ } else {
342
+ result = value
343
+ }
344
+
345
+ if (Number(num) === 0) {
346
+ return result
347
+ } else {
348
+ return dec.greaterThan(new Decimal(0))
349
+ ? removeInvalidZero(result)
350
+ : type === '0'
351
+ ? '-' + removeInvalidZero(result)
352
+ : removeInvalidZero(result)
353
+ }
354
+ } else {
355
+ let value = ''
356
+ // Here it is necessary to judge whether it is greater than 1, and if it is greater than 1, directly retain 8 decimal places
357
+ if (dec.greaterThan(1)) {
358
+ value = dec.toFixed(8, Decimal.ROUND_DOWN)
359
+ } else {
360
+ value = dec.toFixed(precision - length, Decimal.ROUND_DOWN)
361
+ }
362
+
363
+ value = formatePrecision(value, pres).toString()
364
+ // If it is a sats token, round up
365
+ if (integerTokenArr.includes(tokenSymbol)) {
366
+ value = new Decimal(value).toFixed(0, Decimal.ROUND_CEIL)
367
+ }
368
+
369
+ // Do thousandths
370
+ let result = ''
371
+ if (typeof value === 'string') {
372
+ if (value.includes('.')) {
373
+ result = value.replace(/\d(?=(\d{3})+\.)/g, '$&,')
374
+ } else {
375
+ result = toLocalString(value)
376
+ }
377
+ } else {
378
+ result = value
379
+ }
380
+
381
+ return removeInvalidZero(result)
382
+ }
383
+ }) ?? '0.00'
384
+ )
385
+ }
386
+
387
+ /**
388
+ * Format the local fiat currency, with two decimal places by default
389
+ */
390
+ export function formateFaitAmount(num: AmountNum = 0) {
391
+ return (
392
+ tryRun(() => {
393
+ const dec = new Decimal(num)
394
+ const fnum = dec.toFixed(2, Decimal.ROUND_DOWN)
395
+ // If it is greater than 1000, perform thousandths processing
396
+ if (Number(num) >= 1000) {
397
+ let result = toLocalString(fnum)
398
+ if (!result.includes('.')) {
399
+ result = result + '.00'
400
+ }
401
+ return result
402
+ } else {
403
+ return fnum
404
+ }
405
+ }) ?? '0.00'
406
+ )
407
+ }
408
+
409
+ export function tryRun(fn: () => any) {
410
+ try {
411
+ return fn()
412
+ } catch (error: any) {
413
+ return null
414
+ }
415
+ }