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