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.
- package/README.md +38 -0
- package/dist/index.cjs +404 -78
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +359 -76
- package/package.json +1 -1
- package/src/format/index.ts +263 -102
- package/src/index.ts +45 -0
- package/src/string/README.md +441 -0
- package/src/string/index.ts +527 -0
package/src/format/index.ts
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
result +=
|
|
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
|
-
|
|
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'
|