@allkit/shared 0.0.1

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.
Files changed (68) hide show
  1. package/README.md +1 -0
  2. package/dist/README.md +1 -0
  3. package/dist/clipboard/index.d.ts +9 -0
  4. package/dist/cloneDeep/index.d.ts +47 -0
  5. package/dist/constants.d.ts +26 -0
  6. package/dist/cookie/index.d.ts +31 -0
  7. package/dist/date/index.d.ts +133 -0
  8. package/dist/device/index.d.ts +70 -0
  9. package/dist/element/index.d.ts +12 -0
  10. package/dist/index.d.ts +13 -0
  11. package/dist/is/index.d.ts +181 -0
  12. package/dist/lodash/index.d.ts +184 -0
  13. package/dist/number/index.d.ts +84 -0
  14. package/dist/package.json +21 -0
  15. package/dist/shared.es.d.ts +2 -0
  16. package/dist/shared.es.js +1367 -0
  17. package/dist/shared.umd.js +1 -0
  18. package/dist/storage/index.d.ts +144 -0
  19. package/dist/string/index.d.ts +40 -0
  20. package/dist/timer/index.d.ts +9 -0
  21. package/eslint.config.js +10 -0
  22. package/package.json +26 -0
  23. package/scripts/build.mjs +92 -0
  24. package/skill/SKILL.md +240 -0
  25. package/skill/examples/usage.ts +67 -0
  26. package/skill/references/clipboard.md +39 -0
  27. package/skill/references/cloneDeep.md +60 -0
  28. package/skill/references/cookie.md +56 -0
  29. package/skill/references/date.md +466 -0
  30. package/skill/references/device.md +138 -0
  31. package/skill/references/element.md +99 -0
  32. package/skill/references/is.md +415 -0
  33. package/skill/references/lodash.md +472 -0
  34. package/skill/references/number.md +248 -0
  35. package/skill/references/storage.md +113 -0
  36. package/skill/references/string.md +126 -0
  37. package/skill/references/timer.md +78 -0
  38. package/src/clipboard/index.ts +26 -0
  39. package/src/cloneDeep/__test__/cloneDeep.test.ts +92 -0
  40. package/src/cloneDeep/index.ts +168 -0
  41. package/src/constants.ts +27 -0
  42. package/src/cookie/index.ts +44 -0
  43. package/src/date/__test__/date-diff.test.ts +23 -0
  44. package/src/date/__test__/date-duration.test.ts +140 -0
  45. package/src/date/__test__/date-from.test.ts +64 -0
  46. package/src/date/__test__/date.test.ts +67 -0
  47. package/src/date/index.ts +331 -0
  48. package/src/device/__test__/device.test.ts +138 -0
  49. package/src/device/index.ts +125 -0
  50. package/src/element/index.ts +100 -0
  51. package/src/index.ts +14 -0
  52. package/src/is/__test__/is.test.ts +320 -0
  53. package/src/is/index.ts +293 -0
  54. package/src/lodash/__test__/lodash.test.ts +111 -0
  55. package/src/lodash/__test__/obj-string.test.ts +107 -0
  56. package/src/lodash/__test__/uniqueId.test.ts +40 -0
  57. package/src/lodash/index.ts +396 -0
  58. package/src/number/__test__/number.test.ts +137 -0
  59. package/src/number/index.ts +161 -0
  60. package/src/storage/__test__/storage.test.ts +44 -0
  61. package/src/storage/index.ts +185 -0
  62. package/src/string/__test__/string.test.ts +24 -0
  63. package/src/string/index.ts +49 -0
  64. package/src/timer/index.ts +15 -0
  65. package/tsconfig.json +25 -0
  66. package/types/global.d.ts +13 -0
  67. package/vite.config.ts +16 -0
  68. package/vitest.config.ts +28 -0
@@ -0,0 +1,396 @@
1
+ import { isDef, isNullOrUnDef, isObject } from '../is'
2
+
3
+ type OmitUndefined<T> = {
4
+ [K in keyof T]: undefined extends T[K] ? never : T[K]
5
+ }
6
+
7
+ /**
8
+ * 删除对象中的某些键值对
9
+ * @param obj - 源对象
10
+ * @param fields - 需要删除的字段数组
11
+ * @param ignoreEmpty - 是否忽略 undefined | null
12
+ * @returns 返回新的对象
13
+ * @example
14
+ * ```ts
15
+ * const obj = {
16
+ a: 1,
17
+ b: {
18
+ c: 2,
19
+ d: {
20
+ e: 3,
21
+ },
22
+ },
23
+ c: '',
24
+ d: undefined,
25
+ }
26
+ const cloneObj = omit(obj, ['a'])
27
+ * ```
28
+ */
29
+ export function omit<T extends Record<string, any>, K extends keyof T>(
30
+ obj: T,
31
+ fields: K[] | string[],
32
+ ignoreEmpty = true,
33
+ ): Omit<T, K> {
34
+ if (!obj) return {} as Omit<T, K>
35
+ if (!ignoreEmpty) {
36
+ return fields.reduce((result, key) => {
37
+ const { [key]: _, ...rest } = result
38
+ return rest as T
39
+ }, obj)
40
+ }
41
+
42
+ const result = {} as OmitUndefined<T>
43
+ for (const key in obj) {
44
+ // @ts-ignore
45
+ if (!fields.includes(key) && !isNullOrUnDef(obj[key])) {
46
+ result[key] = obj[key]
47
+ }
48
+ }
49
+ return result
50
+ }
51
+
52
+ export type Writeable<T> = { -readonly [P in keyof T]: T[P] }
53
+
54
+ /**
55
+ * 从对象中取出指定的键值对
56
+ * @param obj - 源对象
57
+ * @param keys - 需要取出的键值对
58
+ * @param ignoreEmpty - 是否忽略 undefined
59
+ * @returns 返回新的对象
60
+ * @example
61
+ * ```ts
62
+ * const obj = {
63
+ a: 1,
64
+ b: {
65
+ c: 2,
66
+ d: {
67
+ e: 3,
68
+ },
69
+ },
70
+ }
71
+ const cloneObj = pick(obj, ['b'])
72
+ * ```
73
+ */
74
+ export function pick<T, U extends keyof T>(
75
+ obj: T,
76
+ keys: ReadonlyArray<U> | string[],
77
+ ignoreUndefined = true,
78
+ ) {
79
+ if (!obj) return {} as Writeable<Pick<T, U>>
80
+ return keys.reduce(
81
+ (ret, key) => {
82
+ const value = obj[key as U]
83
+ if (!ignoreUndefined || isDef(value)) {
84
+ ret[key as U] = value
85
+ }
86
+ return ret
87
+ },
88
+ {} as Writeable<Pick<T, U>>,
89
+ )
90
+ }
91
+
92
+ /**
93
+ * 从对象中取出指定的键值对,第二个参数predicate(断言函数)
94
+ * @param obj - 源对象
95
+ * @param predicate - 断言函数,判断为真值的属性会被返回
96
+ * @returns 返回新的对象
97
+ * @example
98
+ * ```ts
99
+ * const obj = {
100
+ a: 1,
101
+ b: {
102
+ c: 2,
103
+ d: {
104
+ e: 3,
105
+ },
106
+ },
107
+ c: '',
108
+ d: undefined,
109
+ }
110
+ const cloneObj = pickBy(obj, (value) => !!value)
111
+ * ```
112
+ */
113
+ export function pickBy<T>(
114
+ obj: T,
115
+ predicate: (val: NonNullable<T>[keyof T], key: keyof T) => boolean,
116
+ ) {
117
+ if (!obj) return {} as Partial<T>
118
+ const ret: Partial<T> = {}
119
+ for (const key in obj) {
120
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
121
+ const typedKey = key as keyof T
122
+ const value = obj[typedKey]
123
+ if (predicate(value, typedKey)) {
124
+ ret[typedKey] = value
125
+ }
126
+ }
127
+ }
128
+ return ret
129
+ }
130
+
131
+ type OmitBy<T, F> = { [K in keyof T as F extends T[K] ? never : K]: T[K] }
132
+
133
+ /**
134
+ * 从对象中去除属性,通过第二个参数predicate(断言函数)
135
+ * 与 {@link pickBy} 反向版
136
+ * @param obj - 源对象
137
+ * @param predicate - 断言函数,判断为真值的属性会被**去除**
138
+ * @returns 返回新的对象
139
+ * @example
140
+ * ```ts
141
+ * const obj = {
142
+ a: 1,
143
+ b: {
144
+ c: 2,
145
+ d: {
146
+ e: 3,
147
+ },
148
+ },
149
+ c: '',
150
+ d: undefined,
151
+ }
152
+ const cloneObj = omitBy(obj, (value) => !value)
153
+ * ```
154
+ */
155
+ export function omitBy<T>(
156
+ object: T,
157
+ predicate: (value: T[keyof T], key: keyof T) => boolean,
158
+ ): OmitBy<T, T[keyof T]> {
159
+ const result = { ...object }
160
+ for (const key in object) {
161
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
162
+ const typedKey = key as keyof T
163
+ const value = object[typedKey]
164
+ if (predicate(value, typedKey)) {
165
+ delete result[key]
166
+ }
167
+ }
168
+ }
169
+ return result
170
+ }
171
+
172
+ /**
173
+ * 防抖函数
174
+ * @param fn - 需要防抖的函数
175
+ * @param delay - 防抖的时间
176
+ * @example
177
+ * ```ts
178
+ * let handleSearch =()=>{}
179
+ * const debounceSearch = debounce(handleSearch)
180
+ * ```
181
+ */
182
+ export const debounce = (fn: Function, delay = 0) => {
183
+ let timer: number
184
+
185
+ return function (this: unknown, ...args: any[]) {
186
+ if (timer) {
187
+ window.clearTimeout(timer)
188
+ }
189
+
190
+ timer = window.setTimeout(() => {
191
+ fn.apply(this, args)
192
+ }, delay)
193
+ }
194
+ }
195
+
196
+ /**
197
+ * 节流函数
198
+ * @param fn - 需要节流的函数
199
+ * @param limit - 节流的时间
200
+ * @param exeLastFunc - 是否执行最后一次函数
201
+ * @example
202
+ * ```ts
203
+ * let handleSearch =()=>{}
204
+ * const throttleSearch = throttle(handleSearch)
205
+ * ```
206
+ */
207
+ export const throttle = <T extends (...args: any) => any>(
208
+ fn: T,
209
+ limit = 200,
210
+ exeLastFunc = true,
211
+ ) => {
212
+ let timer: number
213
+ let start = 0
214
+
215
+ return function loop(this: unknown, ...args: Parameters<T>) {
216
+ const now = Date.now()
217
+ const duration = now - start
218
+ if (duration >= limit) {
219
+ fn.apply(this, args)
220
+ // 更新最后执行时间
221
+ start = now
222
+ } else if (exeLastFunc) {
223
+ window.clearTimeout(timer)
224
+ timer = window.setTimeout(() => {
225
+ fn.apply(this, args)
226
+ start = Date.now()
227
+ }, limit - duration)
228
+ }
229
+ }
230
+ }
231
+
232
+ let counter = 0
233
+ /**
234
+ * 生成当前浏览器唯一id
235
+ * (自增+页面渲染时间+ random随机数)
236
+ * 对于一般的唯一ID 生成来说是足够的
237
+ * @param length -随机数长度 默认16
238
+ * @example
239
+ * ```ts
240
+ * let uid11 = uniqueId(11) //11位
241
+ * let uid16 = uniqueId() //16位
242
+ * let uid19 = uniqueId(19) //19 位
243
+ * let uid32 = uniqueId(32)
244
+ * ```
245
+ * @returns 唯一id
246
+ */
247
+ export function uniqueId(length: number = 16): string {
248
+ const pNow = new Date().getTime().toString()
249
+ const random = Math.random().toString().replace('.', '').slice(1, 10)
250
+ const padLength = length - pNow.length - counter.toString().length - random.length
251
+ const padding = '0'.repeat(padLength > 0 ? padLength : 0)
252
+ const uniqueId = '' + `${counter}${pNow}${padding}${random}`.slice(0, length)
253
+ counter++
254
+ return uniqueId
255
+ }
256
+
257
+ /**
258
+ * 将 Blob 对象转换为 base64 字符串
259
+ * @param blob - Blob 对象
260
+ * @param ignorePrefix - 是否忽略 dateUrl 前缀
261
+ * @example
262
+ * ```ts
263
+ * let base64 = blobToBase64(blob)
264
+ * let base64 = blobToBase64(blob,false)
265
+ * ```
266
+ */
267
+ export function blobToBase64(blob: Blob, ignorePrefix = true): Promise<string> {
268
+ return new Promise((resolve, reject) => {
269
+ const reader = new FileReader()
270
+ reader.onloadend = () => {
271
+ if (typeof reader.result === 'string') {
272
+ if (ignorePrefix) {
273
+ return resolve(reader.result && reader.result.split(',')[1])
274
+ }
275
+ resolve(reader.result)
276
+ } else {
277
+ reject(new Error('Failed to convert Blob to base64'))
278
+ }
279
+ }
280
+ reader.onerror = reject
281
+ reader.readAsDataURL(blob)
282
+ })
283
+ }
284
+
285
+ type DeepKeyOf<T> = T extends object
286
+ ? {
287
+ [K in keyof T]-?: K extends string ? K | `${K}.${DeepKeyOf<T[K]>}` : never
288
+ }[keyof T]
289
+ : never
290
+
291
+ type DeepValueOf<T, K extends string> = K extends `${infer P}.${infer Rest}`
292
+ ? P extends keyof T
293
+ ? DeepValueOf<T[P], Rest>
294
+ : ''
295
+ : K extends keyof T
296
+ ? T[K]
297
+ : ''
298
+
299
+ /**
300
+ * 从对象中获取指定路径的值
301
+ * @param object -对象
302
+ * @param path - 指定路径
303
+ * @example
304
+ * ```ts
305
+ * const obj = {
306
+ * a: { b: { c: 3 } }
307
+ * }
308
+ * const value = get(obj, 'a.b.c')
309
+ * ```
310
+ */
311
+ export function get<T, K extends DeepKeyOf<T>>(obj: T, path: K) {
312
+ const keys = (path as string).split('.')
313
+ let result = obj
314
+
315
+ keys.forEach((key) => {
316
+ result = isObject(result) ? (result[key] ?? '') : ''
317
+ })
318
+ return result as DeepValueOf<T, K>
319
+ }
320
+
321
+ /**
322
+ * 对象转查询字符串
323
+ * @param obj - 对象
324
+ * @returns 查询字符串
325
+ * @example
326
+ * ```ts
327
+ * const str = objToQString({ a: 1, b: 2 })
328
+ * // 输出: 'a=1&b=2'
329
+ * ```
330
+ */
331
+ export function objToQString(obj: Record<string, any>): string {
332
+ if (!obj || typeof obj !== 'object') return ''
333
+ return Object.entries(obj)
334
+ .map(([key, value]) => {
335
+ if (value === undefined || value === null) return ''
336
+ const strValue = typeof value === 'object' ? JSON.stringify(value) : String(value)
337
+ return `${key}=${encodeURIComponent(strValue)}`
338
+ })
339
+ .filter(Boolean)
340
+ .join('&')
341
+ }
342
+
343
+ /**
344
+ * 查询字符串转对象
345
+ * @param qString - 查询字符串
346
+ * @returns 对象
347
+ * @example
348
+ * ```ts
349
+ * const obj = qStringToObj('a=1&b=2')
350
+ * // 输出: { a: 1, b: 2 }
351
+ * ```
352
+ */
353
+ export function qStringToObj(qString: string | null): Record<string, any> {
354
+ if (!qString || typeof qString !== 'string') return {}
355
+
356
+ // 快速检测空字符串
357
+ if (!qString.trim()) return {}
358
+
359
+ // 快速检测完整 URL 且无查询参数
360
+ if (/^https?:\/\/[^?]+$/.test(qString)) return {}
361
+
362
+ // 提取查询参数部分,支持完整 URL 格式
363
+ const queryPart = qString.includes('?') ? qString.split('?')[1] : qString
364
+ if (!queryPart) return {}
365
+
366
+ const result: Record<string, any> = {}
367
+ const pairs = queryPart.split('&')
368
+
369
+ for (const pair of pairs) {
370
+ const [key, value] = pair.split('=')
371
+ if (!key) continue
372
+
373
+ const decodedKey = decodeURIComponent(key)
374
+ const decodedValue = value ? decodeURIComponent(value) : ''
375
+
376
+ // 尝试解析 JSON 值(数组和对象)
377
+ let parsedValue: any = decodedValue
378
+ try {
379
+ parsedValue = JSON.parse(decodedValue)
380
+ } catch {
381
+ // JSON 解析失败,继续处理
382
+ }
383
+
384
+ // 尝试转换为数字(如果不是对象或数组)
385
+ if (typeof parsedValue !== 'object' || parsedValue === null) {
386
+ const num = Number(parsedValue)
387
+ if (!isNaN(num)) {
388
+ parsedValue = num
389
+ }
390
+ }
391
+
392
+ result[decodedKey] = parsedValue
393
+ }
394
+
395
+ return result
396
+ }
@@ -0,0 +1,137 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { formatNumber, formatMoney } from '../'
3
+
4
+ describe('number.ts - formatNumber', () => {
5
+ test('formatNumber - param is null', () => {
6
+ const num = null
7
+ const expected = ''
8
+ //@ts-ignore
9
+ const formatNum = formatNumber(num)
10
+ expect(expected).toEqual(formatNum)
11
+ })
12
+
13
+ test('formatNumber - param is NaN', () => {
14
+ const num = '***'
15
+ const expected = '***'
16
+ const formatNum = formatNumber(num)
17
+ expect(expected).toEqual(formatNum)
18
+ })
19
+
20
+ test('formatNumber - param is number', () => {
21
+ const num = 1000
22
+ const expected = '1,000'
23
+ const formatNum = formatNumber(num)
24
+ expect(expected).toEqual(formatNum)
25
+ })
26
+
27
+ test('formatNumber - param is number', () => {
28
+ const num = 1234567
29
+ const expected = '1,234,567'
30
+ const formatNum = formatNumber(num)
31
+ expect(expected).toEqual(formatNum)
32
+ })
33
+
34
+ test('formatNumber - param is string', () => {
35
+ const num = '1000'
36
+ const expected = '1,000'
37
+ const formatNum = formatNumber(num)
38
+ expect(expected).toEqual(formatNum)
39
+ })
40
+
41
+ test('formatNumber - param is string', () => {
42
+ const num = 1234567
43
+ const expected = '1,234,567'
44
+ const formatNum = formatNumber(num)
45
+ expect(expected).toEqual(formatNum)
46
+ })
47
+
48
+ test('formatNumber - param is 0', () => {
49
+ const num = 0
50
+ const expected = '0'
51
+ const formatNum = formatNumber(num)
52
+ expect(expected).toEqual(formatNum)
53
+ })
54
+
55
+ test('formatNumber - param is decimal', () => {
56
+ const num = 1234.5678
57
+ const expected = '1,235'
58
+ const formatNum = formatNumber(num)
59
+ expect(expected).toEqual(formatNum)
60
+ })
61
+ test('formatNumber - param is negative', () => {
62
+ const num = -1234.5678
63
+ const expected = '-1,235'
64
+ const formatNum = formatNumber(num)
65
+ expect(expected).toEqual(formatNum)
66
+ })
67
+
68
+ test('formatNumber - param is precision eq 2', () => {
69
+ const num = 1234.5678
70
+ const expected = '1,234.57'
71
+ const formatNum = formatNumber(num, { precision: 2 })
72
+ expect(expected).toEqual(formatNum)
73
+ })
74
+
75
+ test('formatNumber - param is precision eq 2', () => {
76
+ const num = 1234
77
+ const expected = '1,234.00'
78
+ const formatNum = formatNumber(num, { precision: 2 })
79
+ expect(expected).toEqual(formatNum)
80
+ })
81
+ })
82
+
83
+ describe('number.ts - formatMoney', () => {
84
+ test('formatMoney - 小于等于4位', () => {
85
+ expect(formatMoney(0)).toBe('0')
86
+ expect(formatMoney(1)).toBe('1')
87
+ expect(formatMoney(999)).toBe('999')
88
+ expect(formatMoney(9999)).toBe('9,999')
89
+ })
90
+
91
+ test('formatMoney - 5-8位 (万)', () => {
92
+ expect(formatMoney(10000)).toBe('1万')
93
+ expect(formatMoney(100000)).toBe('10万')
94
+ expect(formatMoney(1000000)).toBe('100万')
95
+ expect(formatMoney(10000000)).toBe('1,000万')
96
+ expect(formatMoney(12345678)).toBe('1,234万5,678')
97
+ })
98
+
99
+ test('formatMoney - 9-12位 (亿)', () => {
100
+ expect(formatMoney(100000000)).toBe('1亿')
101
+ expect(formatMoney(1000000000)).toBe('10亿')
102
+ expect(formatMoney(1234567890)).toBe('12亿3,456万7,890')
103
+ })
104
+
105
+ test('formatMoney - 大于12位', () => {
106
+ expect(formatMoney(100000000000)).toBe('1,000亿')
107
+ expect(formatMoney(12345678901234)).toBe('123,456亿7890万1234')
108
+ })
109
+
110
+ test('formatMoney - 负数', () => {
111
+ expect(formatMoney(-1234)).toBe('-1,234')
112
+ expect(formatMoney(-10000000)).toBe('-1,000万')
113
+ expect(formatMoney(-100000000)).toBe('-1亿')
114
+ })
115
+
116
+ test('formatMoney - 自定义单位', () => {
117
+ expect(formatMoney(123456789, { yi: 'y', wan: 'w' })).toBe('1y2,345w6,789')
118
+ expect(formatMoney(10000000, { yi: 'Y', wan: 'W' })).toBe('1,000W')
119
+ })
120
+
121
+ test('formatMoney - 禁用千位分隔符', () => {
122
+ expect(formatMoney(123456789, { thousandSeparator: '' })).toBe('1亿2345万6789')
123
+ expect(formatMoney(10000000, { thousandSeparator: '' })).toBe('1000万')
124
+ })
125
+
126
+ test('formatMoney - null/undefined/NaN', () => {
127
+ expect(formatMoney(null as any)).toBe('')
128
+ expect(formatMoney(undefined as any)).toBe('')
129
+ expect(formatMoney(NaN)).toBe('')
130
+ })
131
+
132
+ test('formatMoney - 边界值', () => {
133
+ expect(formatMoney(1000)).toBe('1,000')
134
+ expect(formatMoney(9999)).toBe('9,999')
135
+ expect(formatMoney(10000)).toBe('1万')
136
+ })
137
+ })
@@ -0,0 +1,161 @@
1
+ import Big from 'big.js'
2
+ import { isNullOrUnDef } from '../is'
3
+ import type { RoundingMode } from 'big.js'
4
+
5
+ type BigSource = number | string | Big
6
+
7
+ /**
8
+ * 返回一个实例化的Big对象
9
+ * @param n - 待处理的数字/字符串数字
10
+ * @example
11
+ * ```ts
12
+ * const value = useNumber('1')
13
+ * ```
14
+ */
15
+ export function useNumber(n: BigSource): Big {
16
+ return new Big(n)
17
+ }
18
+
19
+ /**
20
+ * 4舍5入(精度兼容)
21
+ * @param m - 数字
22
+ * @param dp - 保留位数
23
+ * @param rm - 0: 向下取整 1: 四舍五入 2: 向上取整
24
+ * @example
25
+ * ```ts
26
+ * const value = round(5.23, 1, 1)
27
+ * ```
28
+ */
29
+ export function round(m: BigSource, dp: number, rm: RoundingMode = 1) {
30
+ return new Big(m).round(dp, rm).toNumber()
31
+ }
32
+
33
+ export interface FormatOptions {
34
+ /**
35
+ * 保留小数位数
36
+ */
37
+ precision?: number
38
+ /**
39
+ * 千位分隔符
40
+ */
41
+ thousandSeparator?: string
42
+ /**
43
+ * 分割的位数(3位代表千分位分割)
44
+ */
45
+ bit?: number
46
+ /**
47
+ * 0: 向下取整 1: 四舍五入, 2: roundHalfEven, 3: 向上取整
48
+ */
49
+ roundMode?: RoundingMode
50
+ }
51
+
52
+ /**
53
+ * 格式化数字, 默认千位分隔符为","
54
+ * @param val - 要格式化的数字
55
+ * @param options - 配置项
56
+ * @returns 格式化后的数字字符串
57
+ * @example
58
+ * ```ts
59
+ * formatNumber(1000) //输出 "1,000"
60
+ * formatNumber(1234567) //输出 "1,234,567"
61
+ * formatNumber(1234.5678, { precision: 2, thousandSeparator: "-" }) // 输出: "1-234.5678"
62
+ * ```
63
+ */
64
+ export function formatNumber(val: number | string, options: FormatOptions = {}): string {
65
+ const { bit = 3, precision = 0, thousandSeparator = ',', roundMode = 1 } = options
66
+ if (isNullOrUnDef(val)) return ''
67
+ if (isNaN(+val)) return val + ''
68
+ let value = useNumber(val).toFixed(precision, roundMode)
69
+ const arr = value.split('.')
70
+ value = arr[0]
71
+ const reg = new RegExp(`(\\d)(?=(\\d{${bit}})+$)`, 'g')
72
+ value = value.replace(reg, `$1${thousandSeparator}`)
73
+ const decimal = arr.length > 1 ? `.${arr?.[1]}` : ''
74
+ return value + decimal
75
+ }
76
+
77
+ export interface MoneyFormatOptions {
78
+ /**
79
+ * 亿的单位字符串,默认 '亿'
80
+ */
81
+ yi?: string
82
+ /**
83
+ * 万的单位字符串,默认 '万'
84
+ */
85
+ wan?: string
86
+ /**
87
+ * 千位分隔符,默认 ',',传空字符串或不传则不启用千位分隔
88
+ */
89
+ thousandSeparator?: string
90
+ }
91
+
92
+ const DEFAULT_MONEY_OPTIONS: Required<MoneyFormatOptions> = {
93
+ yi: '亿',
94
+ wan: '万',
95
+ thousandSeparator: ',',
96
+ }
97
+
98
+ /**
99
+ * 格式化金钱(支持万、亿)
100
+ * @param num - 数字
101
+ * @param options - 配置项
102
+ * @returns 格式化后的金钱字符串
103
+ * @example
104
+ * ```ts
105
+ * formatMoney(1234567) // '1,234,567'
106
+ * formatMoney(10000000) // '1,000万'
107
+ * formatMoney(100000000) // '1亿'
108
+ * formatMoney(123456789) // '1亿2,345万6,789'
109
+ * formatMoney(123456789, { yi: 'y', wan: 'w' }) // '1y2,345w6,789'
110
+ * formatMoney(123456789, { thousandSeparator: '' }) // '1亿2345万6789'
111
+ * ```
112
+ */
113
+ export function formatMoney(num: number, options: MoneyFormatOptions = {}): string {
114
+ if (isNullOrUnDef(num) || isNaN(num)) return ''
115
+ if (num < 0) return '-' + formatMoney(-num, options)
116
+
117
+ const { yi, wan, thousandSeparator } = {
118
+ ...DEFAULT_MONEY_OPTIONS,
119
+ ...options,
120
+ }
121
+ const len = String(num).length
122
+ const enableSeparator = !!thousandSeparator
123
+
124
+ const addSeparator = (str: string) => {
125
+ if (!enableSeparator) return str
126
+ return str.replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator!)
127
+ }
128
+
129
+ if (len <= 4) return addSeparator(String(num))
130
+
131
+ if (len <= 8) {
132
+ const wanNum = Math.floor(num / 10000)
133
+ const remainder = num % 10000
134
+ return remainder === 0
135
+ ? addSeparator(String(wanNum)) + wan
136
+ : addSeparator(String(wanNum)) + wan + addSeparator(remainder.toString().padStart(4, '0'))
137
+ }
138
+
139
+ if (len <= 12) {
140
+ const yiNum = Math.floor(num / 100000000)
141
+ const wanNum = Math.floor((num % 100000000) / 10000)
142
+ const remainder = num % 10000
143
+
144
+ if (yiNum === 0) return formatMoney(num % 100000000, options)
145
+
146
+ const yiPart = addSeparator(String(yiNum)) + yi
147
+ if (wanNum === 0 && remainder === 0) return yiPart
148
+ if (wanNum === 0) return yiPart + addSeparator(remainder.toString())
149
+ if (remainder === 0) return yiPart + addSeparator(String(wanNum)) + wan
150
+
151
+ return yiPart + addSeparator(String(wanNum)) + wan + addSeparator(remainder.toString())
152
+ }
153
+
154
+ const yiNum = Math.floor(num / 100000000)
155
+ const remainderFromYi = num % 100000000
156
+ return (
157
+ addSeparator(String(yiNum)) +
158
+ yi +
159
+ formatMoney(remainderFromYi, { ...options, thousandSeparator: '' })
160
+ )
161
+ }