@gateweb/react-utils 1.7.0 → 1.8.0

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/es/index.mjs CHANGED
@@ -1,305 +1,153 @@
1
1
  import dayjs from 'dayjs';
2
+ export { Q as QueryProvider, u as useQueryContext } from './queryStore-client-vG-bXFYm.mjs';
2
3
  export { u as useCountdown } from './useCountdown-client-t52WIHfq.mjs';
3
4
  import { useState, useCallback } from 'react';
4
5
  export { d as downloadFile } from './download-client-CnaJ0p_f.mjs';
5
6
  export { g as getLocalStorage, s as setLocalStorage } from './webStorage-client-Pd-loNCg.mjs';
6
7
 
8
+ const FILE_SIZE_UNITS$1 = [
9
+ 'Bytes',
10
+ 'KB',
11
+ 'MB',
12
+ 'GB',
13
+ 'TB',
14
+ 'PB',
15
+ 'EB',
16
+ 'ZB',
17
+ 'YB'
18
+ ];
7
19
  /**
8
- * convert CamelCase string to PascalCase string
9
- * @param str the string to convert
10
- * @example
11
- * camelString2PascalString('camelCase') // 'CamelCase'
12
- * camelString2PascalString('camelCaseTest') // 'CamelCaseTest'
13
- */ const camelString2PascalString = (str)=>`${str[0].toUpperCase()}${str.slice(1)}`;
14
- /**
15
- * convert CamelCase string to SnakeCase string
16
- * @param str the string to convert
17
- * @example
18
- * camelString2SnakeString('camelCase') // 'camel_case'
19
- * camelString2SnakeString('camelCaseTest') // 'camel_case_test'
20
- */ const camelString2SnakeString = (str)=>str.replace(/[A-Z]/g, (letter)=>`_${letter.toLowerCase()}`);
21
- /**
22
- * convert PascalCase string to CamelCase string
23
- * @param str the string to convert
24
- * @example
25
- * pascalString2CamelString('PascalCase') // 'pascalCase'
26
- * pascalString2CamelString('PascalCaseTest') // 'pascalCaseTest'
27
- */ const pascalString2CamelString = (str)=>`${str[0].toLowerCase()}${str.slice(1)}`;
28
- /**
29
- * convert PascalCase string to SnakeCase string
30
- * @param str the string to convert
31
- * @example
32
- * pascalString2SnakeString('PascalCase') // 'pascal_case'
33
- * pascalString2SnakeString('PascalCaseTest') // 'pascal_case_test'
34
- */ const pascalString2SnakeString = (str)=>camelString2SnakeString(pascalString2CamelString(str));
35
- /**
36
- * convert SnakeCase string to CamelCase string
37
- * @param str the string to convert
38
- * @example
39
- * snakeString2CamelString('snake_case') // 'snakeCase'
40
- * snakeString2CamelString('snake_case_test') // 'snakeCaseTest'
41
- */ const snakeString2CamelString = (str)=>str.replace(/(_[a-z])/g, (group)=>group.toUpperCase().replace('_', ''));
42
- /**
43
- * convert SnakeCase string to PascalCase string
44
- * @param str the string to convert
45
- * @example
46
- * snakeString2PascalString('snake_case') // 'SnakeCase'
47
- * snakeString2PascalString('snake_case_test') // 'SnakeCaseTest'
48
- */ const snakeString2PascalString = (str)=>camelString2PascalString(snakeString2CamelString(str));
49
- const transformFun = {
50
- CamelToPascal: camelString2PascalString,
51
- CamelToSnake: camelString2SnakeString,
52
- PascalToCamel: pascalString2CamelString,
53
- PascalToSnake: pascalString2SnakeString,
54
- SnakeToCamel: snakeString2CamelString,
55
- SnakeToPascal: snakeString2PascalString
56
- };
57
- const transformObjectKey = (obj, transformFunName)=>{
58
- if (!obj || typeof obj !== 'object') return obj;
59
- if (Array.isArray(obj)) return obj.map((item)=>transformObjectKey(item, transformFunName));
60
- return Object.keys(obj).reduce((acc, key)=>({
61
- ...acc,
62
- [transformFun[transformFunName](key)]: transformObjectKey(obj[key], transformFunName)
63
- }), {});
64
- };
65
- /**
66
- * convert object key from CamelCase to PascalCase
67
- * @param obj the object to convert
68
- * @example
69
- * const obj = {
70
- * fooBar: 'fooBar',
71
- * fooBar2: 'fooBar2',
72
- * fooBar3: {
73
- * fooBar4: 'fooBar4',
74
- * fooBar5: 'fooBar5',
75
- * },
76
- * };
77
- * const result = camelCase2PascalCase(obj);
78
- * console.log(result); // { FooBar: 'fooBar', FooBar2: 'fooBar2', FooBar3: { FooBar4: 'fooBar4', FooBar5: 'fooBar5' } }
79
- */ const camelCase2PascalCase = (obj)=>transformObjectKey(obj, 'CamelToPascal');
80
- /**
81
- * convert object key from CamelCase to SnakeCase
82
- * @param obj the object to convert
83
- * @example
84
- * const obj = {
85
- * fooBar: 'fooBar',
86
- * fooBar2: 'fooBar2',
87
- * fooBar3: {
88
- * fooBar4: 'fooBar4',
89
- * fooBar5: 'fooBar5',
90
- * },
91
- * };
92
- * const result = camelCase2SnakeCase(obj);
93
- * console.log(result); // { foo_bar: 'fooBar', foo_bar2: 'fooBar2', foo_bar3: { foo_bar4: 'fooBar4', foo_bar5: 'fooBar5' } }
94
- */ const camelCase2SnakeCase = (obj)=>transformObjectKey(obj, 'CamelToSnake');
95
- /**
96
- * convert object key from PascalCase to CamelCase
97
- * @param obj the object to convert
98
- * @example
99
- * const obj = {
100
- * FooBar: 'fooBar',
101
- * FooBar2: 'fooBar2',
102
- * FooBar3: {
103
- * FooBar4: 'fooBar4',
104
- * FooBar5: 'fooBar5',
105
- * },
106
- * };
107
- * const result = pascalCase2CamelCase(obj);
108
- * console.log(result); // { fooBar: 'fooBar', fooBar2: 'fooBar2', fooBar3: { fooBar4: 'fooBar4', fooBar5: 'fooBar5' } }
109
- */ const pascalCase2CamelCase = (obj)=>transformObjectKey(obj, 'PascalToCamel');
110
- /**
111
- * convert object key from PascalCase to SnakeCase
112
- * @param obj the object to convert
113
- * @example
114
- * const obj = {
115
- * FooBar: 'fooBar',
116
- * FooBar2: 'fooBar2',
117
- * FooBar3: {
118
- * FooBar4: 'fooBar4',
119
- * FooBar5: 'fooBar5',
120
- * },
121
- * };
122
- * const result = pascalCase2SnakeCase(obj);
123
- * console.log(result); // { foo_bar: 'fooBar', foo_bar2: 'fooBar2', foo_bar3: { foo_bar4: 'fooBar4', foo_bar5: 'fooBar5' } }
124
- */ const pascalCase2SnakeCase = (obj)=>transformObjectKey(obj, 'PascalToSnake');
125
- /**
126
- * convert object key from SnakeCase to CamelCase
127
- * @param obj the object to convert
128
- * @example
129
- * const obj = {
130
- * foo_bar: 'fooBar',
131
- * foo_bar2: 'fooBar2',
132
- * foo_bar3: {
133
- * foo_bar4: 'fooBar4',
134
- * foo_bar5: 'fooBar5',
135
- * },
136
- * };
137
- * const result = snakeCase2CamelCase(obj);
138
- * console.log(result); // { fooBar: 'fooBar', fooBar2: 'fooBar2', fooBar3: { fooBar4: 'fooBar4', fooBar5: 'fooBar5' } }
139
- */ const snakeCase2CamelCase = (obj)=>transformObjectKey(obj, 'SnakeToCamel');
140
- /**
141
- * convert object key from SnakeCase to PascalCase
142
- * @param obj the object to convert
143
- * @example
144
- * const obj = {
145
- * foo_bar: 'fooBar',
146
- * foo_bar2: 'fooBar2',
147
- * foo_bar3: {
148
- * foo_bar4: 'fooBar4',
149
- * foo_bar5: 'fooBar5',
150
- * },
151
- * };
152
- * const result = snakeCase2PascalCase(obj);
153
- * console.log(result); // { FooBar: 'fooBar', FooBar2: 'fooBar2', FooBar3: { FooBar4: 'fooBar4', FooBar5: 'fooBar5' } }
154
- */ const snakeCase2PascalCase = (obj)=>transformObjectKey(obj, 'SnakeToPascal');
155
-
156
- /**
157
- * 檢查稅務編號是否符合正確的編號規則。
158
- *
159
- * @param {string} taxId - 要檢查的8位數稅務編號。
160
- * @returns {boolean} - 如果符合規則則返回 true,否則返回 false。
161
- *
162
- * ### 編號檢查規則:
163
- * 1. **基本格式檢查**:
164
- * - 編號必須為 8 位數字。
165
- * - 不得為「00000000」或「11111111」,這些編號被視為無效。
166
- *
167
- * 2. **驗證邏輯**:
168
- * - 使用一組驗證運算子:`[1, 2, 1, 2, 1, 2, 4, 1]`。
169
- * - 將稅務編號的每一位數字與對應的運算子相乘後,使用 `calculate` 函數計算各位數的和。
170
- * - 計算公式為:`(product % 10) + (product - (product % 10)) / 10`
171
- * - 將所有位數經計算後的結果加總為 `sum`。
172
- *
173
- * 3. **檢查規則**:
174
- * - 如果總和 `sum` 可以被 5 整除,則編號有效。
175
- * - 或者,若第七位數為 7,且 `(sum + 1) % 5 === 0`,則編號有效。
176
- *
177
- * @example
178
- *
179
- * validTaxId('22099131') // true
180
- * validTaxId('84149961') // true
181
- * validTaxId('00000000') // false
182
- * validTaxId('11111111') // false
183
- * validTaxId('22099132') // false
184
- */ const validTaxId = (taxId)=>{
185
- const invalidList = '00000000,11111111';
186
- if (/^\d{8}$/.test(taxId) === false || invalidList.indexOf(taxId) >= 0) {
187
- return false;
20
+ * 代表字節大小的類,提供各種格式化和轉換方法
21
+ */ class ByteSize {
22
+ /**
23
+ * 建立一個新的 ByteSize 實例
24
+ * @param bytes 位元組數值
25
+ */ constructor(bytes){
26
+ this.bytes = bytes;
188
27
  }
189
- const validateOperator = [
190
- 1,
191
- 2,
192
- 1,
193
- 2,
194
- 1,
195
- 2,
196
- 4,
197
- 1
198
- ];
199
- const calculate = (product)=>product % 10 + (product - product % 10) / 10;
200
- const sum = validateOperator.reduce((pre, cur, index)=>pre + calculate(Number(taxId[index]) * cur), 0);
201
- return sum % 5 === 0 || taxId[6] === '7' && (sum + 1) % 5 === 0;
202
- };
203
- /**
204
- * 驗證日期格式是否正確
205
- *
206
- * @param dateString 日期字串
207
- * @param format 日期格式
208
- * @example
209
- *
210
- * validateDateString('20210201', 'YYYYMMDD') // true
211
- * validateDateString('2021-02-01', 'YYYY-MM-DD') // true
212
- * validateDateString('20210201', 'YYYY-MM-DD') // false
213
- * validateDateString('20210201', 'YYYYMM') // false
214
- */ const validateDateString = (dateString, format)=>{
215
- if (!dateString) return false;
216
- // 根據 format 生成正則表達式
217
- const regexPattern = format.replace(/YYYY/, '\\d{4}').replace(/MM/, '\\d{2}').replace(/DD/, '\\d{2}').replace(/HH/, '\\d{2}').replace(/mm/, '\\d{2}').replace(/ss/, '\\d{2}');
218
- const regex = new RegExp(`^${regexPattern}$`);
219
- // 先用正則驗證格式
220
- if (!regex.test(dateString)) return false;
221
- // 再用 dayjs 驗證是否為有效日期
222
- return dayjs(dateString, format, true).isValid();
223
- };
224
-
225
- /**
226
- * 取得去年今年以及明年期別陣列
227
- *
228
- * @example
229
- * // 假設 今年為 112 年
230
- * generatePeriodArray() // 11102 ~ 11312
231
- */ const generatePeriodArray = ()=>{
232
- const currentYear = new Date().getFullYear() - 1911;
233
- const months = [
234
- '02',
235
- '04',
236
- '06',
237
- '08',
238
- '10',
239
- '12'
240
- ];
241
- const years = [
242
- currentYear - 1,
243
- currentYear,
244
- currentYear + 1
245
- ];
246
- return years.flatMap((year)=>months.map((month)=>`${year}${month}`));
247
- };
28
+ /**
29
+ * 取得原始位元組數值
30
+ */ get value() {
31
+ return this.bytes;
32
+ }
33
+ /**
34
+ * 將位元組轉換為指定單位的數值
35
+ *
36
+ * @param unit 目標單位 (例如 'KB', 'MB', 'GB')
37
+ * @returns 轉換後的數值
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const size = createByteSize(1024);
42
+ * size.to('KB'); // 1
43
+ * size.to('MB'); // 0.0009765625
44
+ * ```
45
+ */ to(unit) {
46
+ if (!FILE_SIZE_UNITS$1.includes(unit)) {
47
+ console.warn(`Invalid unit: ${unit}. Valid units are: ${FILE_SIZE_UNITS$1.join(', ')}`);
48
+ return this.bytes;
49
+ }
50
+ const k = 1024;
51
+ const i = FILE_SIZE_UNITS$1.indexOf(unit);
52
+ return this.bytes / k ** i;
53
+ }
54
+ /**
55
+ * 轉換為適當單位的可讀字符串
56
+ *
57
+ * @param unit 指定單位 (例如 'KB', 'MB', 'GB'),不指定則自動選擇最適合的單位
58
+ * @param decimals 小數點位數 (預設為 2)
59
+ * @returns 格式化後的字符串
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const size = createByteSize(1024);
64
+ * size.format(); // '1.00 KB'
65
+ * size.format('KB'); // '1.00 KB'
66
+ * size.format('MB', 3); // '0.001 MB'
67
+ * ```
68
+ */ format(unit, decimals = 2) {
69
+ if (this.bytes === 0) return `0 ${unit || 'Bytes'}`;
70
+ const k = 1024;
71
+ // 如果指定了有效單位,則使用;否則,計算最適合的單位
72
+ const i = unit && FILE_SIZE_UNITS$1.includes(unit) ? FILE_SIZE_UNITS$1.indexOf(unit) : Math.floor(Math.log(this.bytes) / Math.log(k));
73
+ return `${(this.bytes / k ** i).toFixed(decimals)} ${FILE_SIZE_UNITS$1[i]}`;
74
+ }
75
+ /**
76
+ * 比較兩個 ByteSize 實例
77
+ *
78
+ * @param other 要比較的另一個 ByteSize 實例
79
+ * @returns 比較結果:-1 表示小於,0 表示等於,1 表示大於
80
+ */ compareTo(other) {
81
+ if (this.bytes < other.value) return -1;
82
+ if (this.bytes > other.value) return 1;
83
+ return 0;
84
+ }
85
+ /**
86
+ * 轉換為字符串表示
87
+ * @returns 預設格式化的字符串
88
+ */ toString() {
89
+ return this.format();
90
+ }
91
+ }
248
92
  /**
249
- * 取得當前期別
250
- *
251
- * 報稅期限次期開始15日內
252
- *
253
- * 雙數月沒有特殊規則,期別皆為當月開頭
93
+ * 將位元組轉換為可讀字符串 (保留舊的 API 以確保向後兼容)
254
94
  *
255
- * 單數月15號以前,期別為上個月否則為下個月開頭
95
+ * @param bytes 位元組數值
96
+ * @param unit 目標單位 (例如 'KB', 'MB', 'GB'),不指定則自動選擇最適合的單位
97
+ * @param decimals 小數點位數 (預設為 2)
98
+ * @returns 格式化後的字符串
256
99
  *
257
100
  * @example
258
- *
259
- * // 假設今天是 111-02-15
260
- * getCurrentPeriod() // 11102
261
- * // 假設今天是 111-02-16
262
- * getCurrentPeriod() // 11102
263
- * // 假設今天是 111-03-15
264
- * getCurrentPeriod() // 11102
265
- * // 假設今天是 111-03-16
266
- * getCurrentPeriod() // 11104
267
- */ const getCurrentPeriod = ()=>{
268
- const now = dayjs();
269
- const currentDate = now.format('DD');
270
- const currentYear = now.format('YYYY');
271
- const currentMonth = now.format('MM');
272
- let endMonth;
273
- if (Number(currentMonth) % 2 === 1) {
274
- endMonth = dayjs(`${currentYear}${currentMonth}`, 'YYYYMM').add(Number(currentDate) <= 15 ? -1 : 1, 'month').format('YYYYMM');
275
- } else {
276
- endMonth = `${currentYear}${currentMonth}`;
277
- }
278
- return dayjs(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
279
- };
101
+ * ```ts
102
+ * convertBytes(0) // '0 Bytes'
103
+ * convertBytes(1024, 'KB') // '1 KB'
104
+ * convertBytes(1024, 'KB', 2) // '1.00 KB'
105
+ * convertBytes(1024 * 1024, 'MB') // '1 MB'
106
+ * convertBytes(1024 * 1024, 'KB') // '1024 KB'
107
+ * ```
108
+ */ const convertBytes = (bytes, unit, decimals = 2)=>new ByteSize(bytes).format(unit, decimals);
109
+
280
110
  /**
281
- * 民國年轉西元年
282
- * @param dateString 日期字串
283
- * @param format 日期格式
284
- * @example
111
+ * 檢查兩個值是否相等(包含陣列等引用型別)
112
+ * - 支援基本型別的直接比較
113
+ * - 特別處理空陣列比較
114
+ * - 支援非空陣列的深度比較
285
115
  *
286
- * rocEraToAd('1100201') // 20210201
287
- * rocEraToAd('11002', 'YYYYMM') // 202102
288
- */ const rocEraToAd = (dateString, format = 'YYYYMMDD')=>{
289
- if (!validateDateString(`0${dateString}`, format)) return dateString;
290
- return dayjs(`0${dateString}`, format).add(1911, 'year').format(format);
291
- };
292
- /**
293
- * 西元年轉民國年
294
- * @param dateString 日期字串
295
- * @param format 日期格式
296
- * @example
116
+ * @param value1 - 第一個值
117
+ * @param value2 - 第二個值
118
+ * @returns 如果值相等則返回 true
297
119
  *
298
- * adToRocEra('20210201') // 1100201
299
- * adToRocEra('202102', 'YYYYMM') // 11002
300
- */ const adToRocEra = (dateString, format = 'YYYYMMDD')=>{
301
- if (!validateDateString(dateString, format)) return dateString;
302
- return dayjs(dateString, format).add(-1911, 'year').format(format).substring(1);
120
+ * @example
121
+ * // 基本型別比較
122
+ * isEqual(1, 1); // true
123
+ * isEqual('abc', 'abc'); // true
124
+ * isEqual(null, null); // true
125
+ *
126
+ * // 陣列比較
127
+ * isEqual([], []); // true
128
+ * isEqual([1, 2], [1, 2]); // true
129
+ * isEqual([1, 2], [1, 3]); // false
130
+ */ const isEqual = (value1, value2)=>{
131
+ // 處理基本型別
132
+ if (value1 === value2) {
133
+ return true;
134
+ }
135
+ // 處理空陣列
136
+ if (Array.isArray(value1) && Array.isArray(value2) && value1.length === 0 && value2.length === 0) {
137
+ return true;
138
+ }
139
+ // 兩者都是非 null 的物件(包含陣列)
140
+ if (value1 !== null && value2 !== null && typeof value1 === 'object' && typeof value2 === 'object') {
141
+ // 處理非空陣列
142
+ if (Array.isArray(value1) && Array.isArray(value2)) {
143
+ if (value1.length !== value2.length) {
144
+ return false;
145
+ }
146
+ return value1.every((item, index)=>isEqual(item, value2[index]));
147
+ }
148
+ // 未來可以擴展這裡,增加其他物件型別的深度比較
149
+ }
150
+ return false;
303
151
  };
304
152
 
305
153
  /**
@@ -467,36 +315,6 @@ const transformObjectKey = (obj, transformFunName)=>{
467
315
  }
468
316
  };
469
317
 
470
- /**
471
- * A hook to manage a value.
472
- *
473
- * @example
474
- *
475
- * ```tsx
476
- * const MyComponent = ({ value }: { value?: number }) => {
477
- * const [currentValue, setCurrentValue] = useValue({ value });
478
- * };
479
- * ```
480
- */ const useValue = ({ value, defaultValue })=>{
481
- if (value === undefined && defaultValue === undefined) {
482
- throw new Error('Either `value` or `defaultValue` must be provided.');
483
- }
484
- const isControlled = value !== undefined;
485
- const [internalValue, setInternalValue] = useState(defaultValue);
486
- const setValue = useCallback((newValue)=>{
487
- if (!isControlled) {
488
- setInternalValue(newValue);
489
- }
490
- }, [
491
- isControlled
492
- ]);
493
- const currentValue = isControlled ? value : internalValue;
494
- return [
495
- currentValue,
496
- setValue
497
- ];
498
- };
499
-
500
318
  // const isProduction: boolean = process.env.NODE_ENV === 'production';
501
319
  const prefix = 'Invariant failed';
502
320
  // Throw an error if the condition fails
@@ -552,10 +370,12 @@ message) {
552
370
  * @param values 要排除的 value
553
371
  *
554
372
  * @example
555
- * const a = { a: undefined, b: null, c: 3, d: 4 };
556
- * const b = omitByValue(a, undefined, null); // { c: 3, d: 4 }
373
+ * const a = { a: undefined, b: null, c: 3, d: 4, e: [] };
374
+ * const b = omitByValue(a, undefined, null, []); // { c: 3, d: 4 }
557
375
  */ const omitByValue = (object, ...values)=>Object.entries(object).reduce((acc, [key, value])=>{
558
- if (values.includes(value)) {
376
+ // 使用深度比較檢查值是否應該被排除
377
+ const shouldOmit = values.some((excludeValue)=>isEqual(value, excludeValue));
378
+ if (shouldOmit) {
559
379
  return acc;
560
380
  }
561
381
  return {
@@ -585,10 +405,12 @@ message) {
585
405
  *
586
406
  * @example
587
407
  *
588
- * const a = { a: 1, b: 2, c: 3, d: 4 };
589
- * const b = pickByValue(a, 1, 2); // { a: 1, b: 2 }
408
+ * const a = { a: 1, b: 2, c: 3, d: 4, e: [] };
409
+ * const b = pickByValue(a, 1, 2, []); // { a: 1, b: 2, e: [] }
590
410
  */ const pickByValue = (object, ...values)=>Object.entries(object).reduce((acc, [key, value])=>{
591
- if (values.includes(value)) {
411
+ // 使用深度比較檢查值是否應該被選擇
412
+ const shouldPick = values.some((pickValue)=>isEqual(value, pickValue));
413
+ if (shouldPick) {
592
414
  return {
593
415
  ...acc,
594
416
  [key]: value
@@ -656,44 +478,320 @@ const isObject = (value)=>value !== null && typeof value === 'object';
656
478
  };
657
479
  };
658
480
  /**
659
- * throttle function
481
+ * throttle function
482
+ *
483
+ * @param {Function} fn function to be executed
484
+ * @param {number} delay time to wait before executing the function
485
+ *
486
+ * @example
487
+ * const throttledFunction = throttle((a: number, b: number) => a + b, 1000);
488
+ * throttledFunction(1, 2); // 3
489
+ * throttledFunction(3, 4); // undefined
490
+ * setTimeout(() => {
491
+ * throttledFunction(5, 6); // 11
492
+ * }, 2000);
493
+ */ const throttle = (fn, delay)=>{
494
+ let wait = false;
495
+ return (...args)=>{
496
+ if (wait) return undefined;
497
+ const val = fn(...args);
498
+ wait = true;
499
+ setTimeout(()=>{
500
+ wait = false;
501
+ }, delay);
502
+ return val;
503
+ };
504
+ };
505
+
506
+ /**
507
+ * Wait for a given amount of time
508
+ * @param ms time to wait in milliseconds
509
+ */ const wait = (ms)=>{
510
+ };
511
+
512
+ /**
513
+ * 將單層物件轉換為 URLSearchParams 物件
514
+ * - 會自動排除 null、undefined、空字串和空陣列的值
515
+ * - 陣列會以逗號連接為字串
516
+ *
517
+ * @param obj - 要轉換的物件,只支援單層物件,其中值可以是 string、number、boolean、null、undefined 或字串陣列
518
+ * @returns URLSearchParams 實例
519
+ *
520
+ * @example
521
+ * const params = { a: '1', b: 123, c: false, d: ['1','2'], e: null, f: undefined, g: '', h: [] };
522
+ * objectToSearchParams(params).toString(); // 'a=1&b=123&c=false&d=1%2C2'
523
+ */ const objectToSearchParams = (obj)=>{
524
+ const searchParams = new URLSearchParams();
525
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
526
+ return searchParams;
527
+ }
528
+ Object.entries(omitByValue(obj, undefined, null, '', [])).forEach(([key, value])=>{
529
+ // 如果是陣列,則以逗號連接
530
+ const paramValue = Array.isArray(value) ? value.join(',') : String(value);
531
+ searchParams.append(key, paramValue);
532
+ });
533
+ return searchParams;
534
+ };
535
+ /**
536
+ * 將字串值轉換為指定的型別
537
+ *
538
+ * @param value - 要轉換的字串值
539
+ * @param targetType - 目標型別
540
+ * @returns 轉換後的值
541
+ */ const convertValueByType = (value, targetType)=>{
542
+ switch(targetType){
543
+ case 'number':
544
+ return value ? Number(value) : 0;
545
+ case 'boolean':
546
+ return value === 'true' || value === '1';
547
+ case 'array':
548
+ return value ? value.split(',') : [];
549
+ case 'string':
550
+ default:
551
+ // 預設為字串,不需要轉換
552
+ return value;
553
+ }
554
+ };
555
+ /**
556
+ * 將 URLSearchParams 或字串轉換為物件
557
+ *
558
+ * @param searchParams - 要轉換的搜尋參數字串或 URLSearchParams 實例
559
+ * @param typeMap - 可選的型別轉換映射表,用於指定每個參數的目標型別
560
+ * @returns
561
+ *
562
+ * @example
563
+ * const queryString = 'a=1&b=123&c=true&d=1,2,3&e=false';
564
+ * const result = searchParamsToObject(queryString);
565
+ * // result: { a: '1', b: '123', c: 'true', d: '1,2,3', e: 'false' }
566
+ * const result = searchParamsToObject(queryString, {
567
+ * a: 'string',
568
+ * b: 'number',
569
+ * c: 'boolean',
570
+ * d: 'array',
571
+ * e: 'boolean',
572
+ * });
573
+ * // result: { a: '1', b: 123, c: true, d: ['1', '2', '3'], e: false }
574
+ */ const searchParamsToObject = (searchParams, typeMap = {})=>{
575
+ const searchParamsInstance = typeof searchParams === 'string' ? new URLSearchParams(searchParams) : searchParams;
576
+ const result = {};
577
+ // 從 URLSearchParams 取得所有參數
578
+ searchParamsInstance.forEach((value, key)=>{
579
+ if (!key) {
580
+ return;
581
+ }
582
+ const targetType = typeMap[key];
583
+ result[key] = convertValueByType(value, targetType);
584
+ });
585
+ return result;
586
+ };
587
+
588
+ /**
589
+ * convert CamelCase string to PascalCase string
590
+ * @param str the string to convert
591
+ * @example
592
+ * camelString2PascalString('camelCase') // 'CamelCase'
593
+ * camelString2PascalString('camelCaseTest') // 'CamelCaseTest'
594
+ */ const camelString2PascalString = (str)=>`${str[0].toUpperCase()}${str.slice(1)}`;
595
+ /**
596
+ * convert CamelCase string to SnakeCase string
597
+ * @param str the string to convert
598
+ * @example
599
+ * camelString2SnakeString('camelCase') // 'camel_case'
600
+ * camelString2SnakeString('camelCaseTest') // 'camel_case_test'
601
+ */ const camelString2SnakeString = (str)=>str.replace(/[A-Z]/g, (letter)=>`_${letter.toLowerCase()}`);
602
+ /**
603
+ * convert PascalCase string to CamelCase string
604
+ * @param str the string to convert
605
+ * @example
606
+ * pascalString2CamelString('PascalCase') // 'pascalCase'
607
+ * pascalString2CamelString('PascalCaseTest') // 'pascalCaseTest'
608
+ */ const pascalString2CamelString = (str)=>`${str[0].toLowerCase()}${str.slice(1)}`;
609
+ /**
610
+ * convert PascalCase string to SnakeCase string
611
+ * @param str the string to convert
612
+ * @example
613
+ * pascalString2SnakeString('PascalCase') // 'pascal_case'
614
+ * pascalString2SnakeString('PascalCaseTest') // 'pascal_case_test'
615
+ */ const pascalString2SnakeString = (str)=>camelString2SnakeString(pascalString2CamelString(str));
616
+ /**
617
+ * convert SnakeCase string to CamelCase string
618
+ * @param str the string to convert
619
+ * @example
620
+ * snakeString2CamelString('snake_case') // 'snakeCase'
621
+ * snakeString2CamelString('snake_case_test') // 'snakeCaseTest'
622
+ */ const snakeString2CamelString = (str)=>str.replace(/(_[a-z])/g, (group)=>group.toUpperCase().replace('_', ''));
623
+ /**
624
+ * convert SnakeCase string to PascalCase string
625
+ * @param str the string to convert
626
+ * @example
627
+ * snakeString2PascalString('snake_case') // 'SnakeCase'
628
+ * snakeString2PascalString('snake_case_test') // 'SnakeCaseTest'
629
+ */ const snakeString2PascalString = (str)=>camelString2PascalString(snakeString2CamelString(str));
630
+ const transformFun = {
631
+ CamelToPascal: camelString2PascalString,
632
+ CamelToSnake: camelString2SnakeString,
633
+ PascalToCamel: pascalString2CamelString,
634
+ PascalToSnake: pascalString2SnakeString,
635
+ SnakeToCamel: snakeString2CamelString,
636
+ SnakeToPascal: snakeString2PascalString
637
+ };
638
+ const transformObjectKey = (obj, transformFunName)=>{
639
+ if (!obj || typeof obj !== 'object') return obj;
640
+ if (Array.isArray(obj)) return obj.map((item)=>transformObjectKey(item, transformFunName));
641
+ return Object.keys(obj).reduce((acc, key)=>({
642
+ ...acc,
643
+ [transformFun[transformFunName](key)]: transformObjectKey(obj[key], transformFunName)
644
+ }), {});
645
+ };
646
+ /**
647
+ * convert object key from CamelCase to PascalCase
648
+ * @param obj the object to convert
649
+ * @example
650
+ * const obj = {
651
+ * fooBar: 'fooBar',
652
+ * fooBar2: 'fooBar2',
653
+ * fooBar3: {
654
+ * fooBar4: 'fooBar4',
655
+ * fooBar5: 'fooBar5',
656
+ * },
657
+ * };
658
+ * const result = camelCase2PascalCase(obj);
659
+ * console.log(result); // { FooBar: 'fooBar', FooBar2: 'fooBar2', FooBar3: { FooBar4: 'fooBar4', FooBar5: 'fooBar5' } }
660
+ */ const camelCase2PascalCase = (obj)=>transformObjectKey(obj, 'CamelToPascal');
661
+ /**
662
+ * convert object key from CamelCase to SnakeCase
663
+ * @param obj the object to convert
664
+ * @example
665
+ * const obj = {
666
+ * fooBar: 'fooBar',
667
+ * fooBar2: 'fooBar2',
668
+ * fooBar3: {
669
+ * fooBar4: 'fooBar4',
670
+ * fooBar5: 'fooBar5',
671
+ * },
672
+ * };
673
+ * const result = camelCase2SnakeCase(obj);
674
+ * console.log(result); // { foo_bar: 'fooBar', foo_bar2: 'fooBar2', foo_bar3: { foo_bar4: 'fooBar4', foo_bar5: 'fooBar5' } }
675
+ */ const camelCase2SnakeCase = (obj)=>transformObjectKey(obj, 'CamelToSnake');
676
+ /**
677
+ * convert object key from PascalCase to CamelCase
678
+ * @param obj the object to convert
679
+ * @example
680
+ * const obj = {
681
+ * FooBar: 'fooBar',
682
+ * FooBar2: 'fooBar2',
683
+ * FooBar3: {
684
+ * FooBar4: 'fooBar4',
685
+ * FooBar5: 'fooBar5',
686
+ * },
687
+ * };
688
+ * const result = pascalCase2CamelCase(obj);
689
+ * console.log(result); // { fooBar: 'fooBar', fooBar2: 'fooBar2', fooBar3: { fooBar4: 'fooBar4', fooBar5: 'fooBar5' } }
690
+ */ const pascalCase2CamelCase = (obj)=>transformObjectKey(obj, 'PascalToCamel');
691
+ /**
692
+ * convert object key from PascalCase to SnakeCase
693
+ * @param obj the object to convert
694
+ * @example
695
+ * const obj = {
696
+ * FooBar: 'fooBar',
697
+ * FooBar2: 'fooBar2',
698
+ * FooBar3: {
699
+ * FooBar4: 'fooBar4',
700
+ * FooBar5: 'fooBar5',
701
+ * },
702
+ * };
703
+ * const result = pascalCase2SnakeCase(obj);
704
+ * console.log(result); // { foo_bar: 'fooBar', foo_bar2: 'fooBar2', foo_bar3: { foo_bar4: 'fooBar4', foo_bar5: 'fooBar5' } }
705
+ */ const pascalCase2SnakeCase = (obj)=>transformObjectKey(obj, 'PascalToSnake');
706
+ /**
707
+ * convert object key from SnakeCase to CamelCase
708
+ * @param obj the object to convert
709
+ * @example
710
+ * const obj = {
711
+ * foo_bar: 'fooBar',
712
+ * foo_bar2: 'fooBar2',
713
+ * foo_bar3: {
714
+ * foo_bar4: 'fooBar4',
715
+ * foo_bar5: 'fooBar5',
716
+ * },
717
+ * };
718
+ * const result = snakeCase2CamelCase(obj);
719
+ * console.log(result); // { fooBar: 'fooBar', fooBar2: 'fooBar2', fooBar3: { fooBar4: 'fooBar4', fooBar5: 'fooBar5' } }
720
+ */ const snakeCase2CamelCase = (obj)=>transformObjectKey(obj, 'SnakeToCamel');
721
+ /**
722
+ * convert object key from SnakeCase to PascalCase
723
+ * @param obj the object to convert
724
+ * @example
725
+ * const obj = {
726
+ * foo_bar: 'fooBar',
727
+ * foo_bar2: 'fooBar2',
728
+ * foo_bar3: {
729
+ * foo_bar4: 'fooBar4',
730
+ * foo_bar5: 'fooBar5',
731
+ * },
732
+ * };
733
+ * const result = snakeCase2PascalCase(obj);
734
+ * console.log(result); // { FooBar: 'fooBar', FooBar2: 'fooBar2', FooBar3: { FooBar4: 'fooBar4', FooBar5: 'fooBar5' } }
735
+ */ const snakeCase2PascalCase = (obj)=>transformObjectKey(obj, 'SnakeToPascal');
736
+
737
+ /**
738
+ * 將數字轉換成金額千分位格式
739
+ *
740
+ * @param num - 數字
741
+ *
742
+ * @example
743
+ *
744
+ * formatAmount(1234567) // '1,234,567'
745
+ */ const formatAmount = (num)=>{
746
+ if (typeof num !== 'number') return '';
747
+ return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
748
+ };
749
+ /**
750
+ * 將字串的第 n - m 個字轉換成星號
751
+ *
752
+ * @param str - 字串
753
+ * @param n - 起始位置
754
+ * @param m - 結束位置
755
+ *
756
+ * @example
757
+ *
758
+ * formatString('123456', 1, 4) // '1****6'
759
+ */ const formatStarMask = (str, n, m)=>str.slice(0, n) + '*'.repeat(m - n + 1) + str.slice(m + 1);
760
+ const FILE_SIZE_UNITS = [
761
+ 'Bytes',
762
+ 'KB',
763
+ 'MB',
764
+ 'GB',
765
+ 'TB',
766
+ 'PB',
767
+ 'EB',
768
+ 'ZB',
769
+ 'YB'
770
+ ];
771
+ /**
772
+ * format file size to human readable string
660
773
  *
661
- * @param {Function} fn function to be executed
662
- * @param {number} delay time to wait before executing the function
774
+ * it will convert bytes to KB, MB, GB, TB, PB, EB, ZB, YB
775
+ *
776
+ * @param bytes file size in bytes
777
+ * @param decimals number of decimal places (default is 2)
663
778
  *
664
779
  * @example
665
- * const throttledFunction = throttle((a: number, b: number) => a + b, 1000);
666
- * throttledFunction(1, 2); // 3
667
- * throttledFunction(3, 4); // undefined
668
- * setTimeout(() => {
669
- * throttledFunction(5, 6); // 11
670
- * }, 2000);
671
- */ const throttle = (fn, delay)=>{
672
- let wait = false;
673
- return (...args)=>{
674
- if (wait) return undefined;
675
- const val = fn(...args);
676
- wait = true;
677
- setTimeout(()=>{
678
- wait = false;
679
- }, delay);
680
- return val;
681
- };
780
+ *
781
+ * ```js
782
+ * formatBytes(0) // 0 Bytes
783
+ * formatBytes(1024) // 1 KB
784
+ * formatBytes(1024, 2) // 1.00 KB
785
+ * formatBytes(1024 * 1024) // 1 MB
786
+ * ```
787
+ * @deprecated use `convertBytes` instead
788
+ */ const formatBytes = (bytes, decimals = 0)=>{
789
+ if (bytes === 0) return '0 Bytes';
790
+ const k = 1024;
791
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
792
+ return `${(bytes / k ** i).toFixed(decimals)} ${FILE_SIZE_UNITS[i]}`;
682
793
  };
683
794
 
684
- function mergeRefs(refs) {
685
- return (value)=>{
686
- refs.forEach((ref)=>{
687
- if (typeof ref === 'function') {
688
- ref(value);
689
- } else if (ref != null) {
690
- // eslint-disable-next-line no-param-reassign
691
- ref.current = value;
692
- }
693
- });
694
- };
695
- }
696
-
697
795
  /**
698
796
  * 中文
699
797
  *
@@ -862,66 +960,196 @@ function mergeRefs(refs) {
862
960
  */ const isTWPhone = ()=>/^(((0[2-9]-?\d{6,8})(#\d{1,5})?)|((0[2-9][0-9]-?\d{6,7})(#\d{1,5})?))$/;
863
961
 
864
962
  /**
865
- * 將數字轉換成金額千分位格式
963
+ * 檢查稅務編號是否符合正確的編號規則。
866
964
  *
867
- * @param num - 數字
965
+ * @param {string} taxId - 要檢查的8位數稅務編號。
966
+ * @returns {boolean} - 如果符合規則則返回 true,否則返回 false。
868
967
  *
869
- * @example
968
+ * ### 編號檢查規則:
969
+ * 1. **基本格式檢查**:
970
+ * - 編號必須為 8 位數字。
971
+ * - 不得為「00000000」或「11111111」,這些編號被視為無效。
870
972
  *
871
- * formatAmount(1234567) // '1,234,567'
872
- */ const formatAmount = (num)=>{
873
- if (typeof num !== 'number') return '';
874
- return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
875
- };
876
- /**
877
- * 將字串的第 n - m 個字轉換成星號
973
+ * 2. **驗證邏輯**:
974
+ * - 使用一組驗證運算子:`[1, 2, 1, 2, 1, 2, 4, 1]`。
975
+ * - 將稅務編號的每一位數字與對應的運算子相乘後,使用 `calculate` 函數計算各位數的和。
976
+ * - 計算公式為:`(product % 10) + (product - (product % 10)) / 10`
977
+ * - 將所有位數經計算後的結果加總為 `sum`。
878
978
  *
879
- * @param str - 字串
880
- * @param n - 起始位置
881
- * @param m - 結束位置
979
+ * 3. **檢查規則**:
980
+ * - 如果總和 `sum` 可以被 5 整除,則編號有效。
981
+ * - 或者,若第七位數為 7,且 `(sum + 1) % 5 === 0`,則編號有效。
882
982
  *
883
983
  * @example
884
984
  *
885
- * formatString('123456', 1, 4) // '1****6'
886
- */ const formatStarMask = (str, n, m)=>str.slice(0, n) + '*'.repeat(m - n + 1) + str.slice(m + 1);
985
+ * validTaxId('22099131') // true
986
+ * validTaxId('84149961') // true
987
+ * validTaxId('00000000') // false
988
+ * validTaxId('11111111') // false
989
+ * validTaxId('22099132') // false
990
+ */ const validTaxId = (taxId)=>{
991
+ const invalidList = '00000000,11111111';
992
+ if (/^\d{8}$/.test(taxId) === false || invalidList.indexOf(taxId) >= 0) {
993
+ return false;
994
+ }
995
+ const validateOperator = [
996
+ 1,
997
+ 2,
998
+ 1,
999
+ 2,
1000
+ 1,
1001
+ 2,
1002
+ 4,
1003
+ 1
1004
+ ];
1005
+ const calculate = (product)=>product % 10 + (product - product % 10) / 10;
1006
+ const sum = validateOperator.reduce((pre, cur, index)=>pre + calculate(Number(taxId[index]) * cur), 0);
1007
+ return sum % 5 === 0 || taxId[6] === '7' && (sum + 1) % 5 === 0;
1008
+ };
887
1009
  /**
888
- * format file size to human readable string
1010
+ * 驗證日期格式是否正確
889
1011
  *
890
- * it will convert bytes to KB, MB, GB, TB, PB, EB, ZB, YB
1012
+ * @param dateString 日期字串
1013
+ * @param format 日期格式
1014
+ * @example
891
1015
  *
892
- * @param bytes file size in bytes
893
- * @param decimals number of decimal places (default is 2)
1016
+ * validateDateString('20210201', 'YYYYMMDD') // true
1017
+ * validateDateString('2021-02-01', 'YYYY-MM-DD') // true
1018
+ * validateDateString('20210201', 'YYYY-MM-DD') // false
1019
+ * validateDateString('20210201', 'YYYYMM') // false
1020
+ */ const validateDateString = (dateString, format)=>{
1021
+ if (!dateString) return false;
1022
+ // 根據 format 生成正則表達式
1023
+ const regexPattern = format.replace(/YYYY/, '\\d{4}').replace(/MM/, '\\d{2}').replace(/DD/, '\\d{2}').replace(/HH/, '\\d{2}').replace(/mm/, '\\d{2}').replace(/ss/, '\\d{2}');
1024
+ const regex = new RegExp(`^${regexPattern}$`);
1025
+ // 先用正則驗證格式
1026
+ if (!regex.test(dateString)) return false;
1027
+ // 再用 dayjs 驗證是否為有效日期
1028
+ return dayjs(dateString, format, true).isValid();
1029
+ };
1030
+
1031
+ /**
1032
+ * A hook to manage a value.
894
1033
  *
895
1034
  * @example
896
1035
  *
897
- * ```js
898
- * formatBytes(0) // 0 Bytes
899
- * formatBytes(1024) // 1 KB
900
- * formatBytes(1024, 2) // 1.00 KB
901
- * formatBytes(1024 * 1024) // 1 MB
1036
+ * ```tsx
1037
+ * const MyComponent = ({ value }: { value?: number }) => {
1038
+ * const [currentValue, setCurrentValue] = useValue({ value });
1039
+ * };
902
1040
  * ```
903
- */ const formatBytes = (bytes, decimals = 0)=>{
904
- if (bytes === 0) return '0 Bytes';
905
- const k = 1024;
906
- const sizes = [
907
- 'Bytes',
908
- 'KB',
909
- 'MB',
910
- 'GB',
911
- 'TB',
912
- 'PB',
913
- 'EB',
914
- 'ZB',
915
- 'YB'
1041
+ */ const useValue = ({ value, defaultValue })=>{
1042
+ if (value === undefined && defaultValue === undefined) {
1043
+ throw new Error('Either `value` or `defaultValue` must be provided.');
1044
+ }
1045
+ const isControlled = value !== undefined;
1046
+ const [internalValue, setInternalValue] = useState(defaultValue);
1047
+ const setValue = useCallback((newValue)=>{
1048
+ if (!isControlled) {
1049
+ setInternalValue(newValue);
1050
+ }
1051
+ }, [
1052
+ isControlled
1053
+ ]);
1054
+ const currentValue = isControlled ? value : internalValue;
1055
+ return [
1056
+ currentValue,
1057
+ setValue
916
1058
  ];
917
- const i = Math.floor(Math.log(bytes) / Math.log(k));
918
- return `${(bytes / k ** i).toFixed(decimals)} ${sizes[i]}`;
919
1059
  };
920
1060
 
1061
+ function mergeRefs(refs) {
1062
+ return (value)=>{
1063
+ refs.forEach((ref)=>{
1064
+ if (typeof ref === 'function') {
1065
+ ref(value);
1066
+ } else if (ref != null) {
1067
+ // eslint-disable-next-line no-param-reassign
1068
+ ref.current = value;
1069
+ }
1070
+ });
1071
+ };
1072
+ }
1073
+
921
1074
  /**
922
- * Wait for a given amount of time
923
- * @param ms time to wait in milliseconds
924
- */ const wait = (ms)=>{
1075
+ * 民國年轉西元年
1076
+ * @param dateString 日期字串
1077
+ * @param format 日期格式
1078
+ * @example
1079
+ *
1080
+ * rocEraToAd('1100201') // 20210201
1081
+ * rocEraToAd('11002', 'YYYYMM') // 202102
1082
+ */ const rocEraToAd = (dateString, format = 'YYYYMMDD')=>{
1083
+ if (!validateDateString(`0${dateString}`, format)) return dateString;
1084
+ return dayjs(`0${dateString}`, format).add(1911, 'year').format(format);
1085
+ };
1086
+ /**
1087
+ * 西元年轉民國年
1088
+ * @param dateString 日期字串
1089
+ * @param format 日期格式
1090
+ * @example
1091
+ *
1092
+ * adToRocEra('20210201') // 1100201
1093
+ * adToRocEra('202102', 'YYYYMM') // 11002
1094
+ */ const adToRocEra = (dateString, format = 'YYYYMMDD')=>{
1095
+ if (!validateDateString(dateString, format)) return dateString;
1096
+ return dayjs(dateString, format).add(-1911, 'year').format(format).substring(1);
1097
+ };
1098
+
1099
+ /**
1100
+ * 取得去年今年以及明年期別陣列
1101
+ *
1102
+ * @example
1103
+ * // 假設 今年為 112 年
1104
+ * generatePeriodArray() // 11102 ~ 11312
1105
+ */ const generatePeriodArray = ()=>{
1106
+ const currentYear = new Date().getFullYear() - 1911;
1107
+ const months = [
1108
+ '02',
1109
+ '04',
1110
+ '06',
1111
+ '08',
1112
+ '10',
1113
+ '12'
1114
+ ];
1115
+ const years = [
1116
+ currentYear - 1,
1117
+ currentYear,
1118
+ currentYear + 1
1119
+ ];
1120
+ return years.flatMap((year)=>months.map((month)=>`${year}${month}`));
1121
+ };
1122
+ /**
1123
+ * 取得當前期別
1124
+ *
1125
+ * 報稅期限次期開始15日內
1126
+ *
1127
+ * 雙數月沒有特殊規則,期別皆為當月開頭
1128
+ *
1129
+ * 單數月15號以前,期別為上個月否則為下個月開頭
1130
+ *
1131
+ * @example
1132
+ *
1133
+ * // 假設今天是 111-02-15
1134
+ * getCurrentPeriod() // 11102
1135
+ * // 假設今天是 111-02-16
1136
+ * getCurrentPeriod() // 11102
1137
+ * // 假設今天是 111-03-15
1138
+ * getCurrentPeriod() // 11102
1139
+ * // 假設今天是 111-03-16
1140
+ * getCurrentPeriod() // 11104
1141
+ */ const getCurrentPeriod = ()=>{
1142
+ const now = dayjs();
1143
+ const currentDate = now.format('DD');
1144
+ const currentYear = now.format('YYYY');
1145
+ const currentMonth = now.format('MM');
1146
+ let endMonth;
1147
+ if (Number(currentMonth) % 2 === 1) {
1148
+ endMonth = dayjs(`${currentYear}${currentMonth}`, 'YYYYMM').add(Number(currentDate) <= 15 ? -1 : 1, 'month').format('YYYYMM');
1149
+ } else {
1150
+ endMonth = `${currentYear}${currentMonth}`;
1151
+ }
1152
+ return dayjs(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
925
1153
  };
926
1154
 
927
- export { adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, createEnumLikeObject, debounce, deepMerge, extractEnumLikeObject, fakeApi, formatAmount, formatBytes, formatStarMask, generatePeriodArray, getCurrentPeriod, getMimeType, invariant, isChinese, isDateString, isDateTimeString, isEmail, isEnglish, isNonZeroStart, isNumber, isNumberAtLeastN, isNumberN, isNumberNM, isServer, isTWMobile, isTWPhone, isTimeString, isValidPassword, mergeRefs, omit, omitByValue, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, rocEraToAd, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, useValue, validTaxId, validateDateString, validateFileType, wait };
1155
+ export { ByteSize, adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, convertBytes, createEnumLikeObject, debounce, deepMerge, extractEnumLikeObject, fakeApi, formatAmount, formatBytes, formatStarMask, generatePeriodArray, getCurrentPeriod, getMimeType, invariant, isChinese, isDateString, isDateTimeString, isEmail, isEnglish, isEqual, isNonZeroStart, isNumber, isNumberAtLeastN, isNumberN, isNumberNM, isServer, isTWMobile, isTWPhone, isTimeString, isValidPassword, mergeRefs, objectToSearchParams, omit, omitByValue, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, pick, pickByValue, rocEraToAd, searchParamsToObject, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, useValue, validTaxId, validateDateString, validateFileType, wait };