react-util-tools 1.0.25 → 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.
@@ -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
+ }
package/src/index.ts CHANGED
@@ -149,3 +149,48 @@ export type {
149
149
  Sheet2JSONOpts,
150
150
  JSON2SheetOpts
151
151
  } from './excel/index'
152
+ export {
153
+ capitalize,
154
+ camelCase,
155
+ pascalCase,
156
+ snakeCase,
157
+ kebabCase,
158
+ truncate,
159
+ trim,
160
+ trimStart,
161
+ trimEnd,
162
+ reverse,
163
+ repeat,
164
+ padStart,
165
+ padEnd,
166
+ startsWith,
167
+ endsWith,
168
+ includes,
169
+ replaceAll,
170
+ stripHtml,
171
+ escapeHtml,
172
+ unescapeHtml,
173
+ toLowerCase,
174
+ toUpperCase,
175
+ titleCase,
176
+ isEmpty,
177
+ isNotEmpty,
178
+ length,
179
+ split,
180
+ extractNumbers,
181
+ removeSpaces,
182
+ normalizeSpaces,
183
+ countOccurrences,
184
+ randomString,
185
+ uuid,
186
+ maskPhone,
187
+ maskIdCard,
188
+ maskBankCard,
189
+ maskName,
190
+ isValidPhone,
191
+ isValidEmail,
192
+ isValidUrl,
193
+ isValidIdCard,
194
+ toBase64,
195
+ fromBase64
196
+ } from './string/index'