@gateweb/react-utils 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.
- package/README.md +2 -0
- package/dist/cjs/download-client-DKxkL92w.js +27 -0
- package/dist/cjs/index.d.ts +620 -0
- package/dist/cjs/index.js +716 -0
- package/dist/cjs/useCountdown-client-CNjGBIUB.js +68 -0
- package/dist/cjs/webStorage-client-BGQKUfrO.js +53 -0
- package/dist/es/download-client-CnaJ0p_f.mjs +27 -0
- package/dist/es/index.d.mts +620 -0
- package/dist/es/index.mjs +666 -0
- package/dist/es/useCountdown-client-t52WIHfq.mjs +68 -0
- package/dist/es/webStorage-client-Pd-loNCg.mjs +52 -0
- package/package.json +62 -0
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
export { u as useCountdown } from './useCountdown-client-t52WIHfq.mjs';
|
|
3
|
+
export { d as downloadFile } from './download-client-CnaJ0p_f.mjs';
|
|
4
|
+
export { g as getLocalStorage, s as setLocalStorage } from './webStorage-client-Pd-loNCg.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* convert CamelCase string to PascalCase string
|
|
8
|
+
* @param str the string to convert
|
|
9
|
+
* @example
|
|
10
|
+
* camelString2PascalString('camelCase') // 'CamelCase'
|
|
11
|
+
* camelString2PascalString('camelCaseTest') // 'CamelCaseTest'
|
|
12
|
+
*/ const camelString2PascalString = (str)=>`${str[0].toUpperCase()}${str.slice(1)}`;
|
|
13
|
+
/**
|
|
14
|
+
* convert CamelCase string to SnakeCase string
|
|
15
|
+
* @param str the string to convert
|
|
16
|
+
* @example
|
|
17
|
+
* camelString2SnakeString('camelCase') // 'camel_case'
|
|
18
|
+
* camelString2SnakeString('camelCaseTest') // 'camel_case_test'
|
|
19
|
+
*/ const camelString2SnakeString = (str)=>str.replace(/[A-Z]/g, (letter)=>`_${letter.toLowerCase()}`);
|
|
20
|
+
/**
|
|
21
|
+
* convert PascalCase string to CamelCase string
|
|
22
|
+
* @param str the string to convert
|
|
23
|
+
* @example
|
|
24
|
+
* pascalString2CamelString('PascalCase') // 'pascalCase'
|
|
25
|
+
* pascalString2CamelString('PascalCaseTest') // 'pascalCaseTest'
|
|
26
|
+
*/ const pascalString2CamelString = (str)=>`${str[0].toLowerCase()}${str.slice(1)}`;
|
|
27
|
+
/**
|
|
28
|
+
* convert PascalCase string to SnakeCase string
|
|
29
|
+
* @param str the string to convert
|
|
30
|
+
* @example
|
|
31
|
+
* pascalString2SnakeString('PascalCase') // 'pascal_case'
|
|
32
|
+
* pascalString2SnakeString('PascalCaseTest') // 'pascal_case_test'
|
|
33
|
+
*/ const pascalString2SnakeString = (str)=>camelString2SnakeString(pascalString2CamelString(str));
|
|
34
|
+
/**
|
|
35
|
+
* convert SnakeCase string to CamelCase string
|
|
36
|
+
* @param str the string to convert
|
|
37
|
+
* @example
|
|
38
|
+
* snakeString2CamelString('snake_case') // 'snakeCase'
|
|
39
|
+
* snakeString2CamelString('snake_case_test') // 'snakeCaseTest'
|
|
40
|
+
*/ const snakeString2CamelString = (str)=>str.replace(/(_[a-z])/g, (group)=>group.toUpperCase().replace('_', ''));
|
|
41
|
+
/**
|
|
42
|
+
* convert SnakeCase string to PascalCase string
|
|
43
|
+
* @param str the string to convert
|
|
44
|
+
* @example
|
|
45
|
+
* snakeString2PascalString('snake_case') // 'SnakeCase'
|
|
46
|
+
* snakeString2PascalString('snake_case_test') // 'SnakeCaseTest'
|
|
47
|
+
*/ const snakeString2PascalString = (str)=>camelString2PascalString(snakeString2CamelString(str));
|
|
48
|
+
const transformFun = {
|
|
49
|
+
CamelToPascal: camelString2PascalString,
|
|
50
|
+
CamelToSnake: camelString2SnakeString,
|
|
51
|
+
PascalToCamel: pascalString2CamelString,
|
|
52
|
+
PascalToSnake: pascalString2SnakeString,
|
|
53
|
+
SnakeToCamel: snakeString2CamelString,
|
|
54
|
+
SnakeToPascal: snakeString2PascalString
|
|
55
|
+
};
|
|
56
|
+
const transformObjectKey = (obj, transformFunName)=>{
|
|
57
|
+
if (!obj || typeof obj !== 'object') return obj;
|
|
58
|
+
if (Array.isArray(obj)) return obj.map((item)=>transformObjectKey(item, transformFunName));
|
|
59
|
+
return Object.keys(obj).reduce((acc, key)=>({
|
|
60
|
+
...acc,
|
|
61
|
+
[transformFun[transformFunName](key)]: transformObjectKey(obj[key], transformFunName)
|
|
62
|
+
}), {});
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* convert object key from CamelCase to PascalCase
|
|
66
|
+
* @param obj the object to convert
|
|
67
|
+
* @example
|
|
68
|
+
* const obj = {
|
|
69
|
+
* fooBar: 'fooBar',
|
|
70
|
+
* fooBar2: 'fooBar2',
|
|
71
|
+
* fooBar3: {
|
|
72
|
+
* fooBar4: 'fooBar4',
|
|
73
|
+
* fooBar5: 'fooBar5',
|
|
74
|
+
* },
|
|
75
|
+
* };
|
|
76
|
+
* const result = camelCase2PascalCase(obj);
|
|
77
|
+
* console.log(result); // { FooBar: 'fooBar', FooBar2: 'fooBar2', FooBar3: { FooBar4: 'fooBar4', FooBar5: 'fooBar5' } }
|
|
78
|
+
*/ const camelCase2PascalCase = (obj)=>transformObjectKey(obj, 'CamelToPascal');
|
|
79
|
+
/**
|
|
80
|
+
* convert object key from CamelCase to SnakeCase
|
|
81
|
+
* @param obj the object to convert
|
|
82
|
+
* @example
|
|
83
|
+
* const obj = {
|
|
84
|
+
* fooBar: 'fooBar',
|
|
85
|
+
* fooBar2: 'fooBar2',
|
|
86
|
+
* fooBar3: {
|
|
87
|
+
* fooBar4: 'fooBar4',
|
|
88
|
+
* fooBar5: 'fooBar5',
|
|
89
|
+
* },
|
|
90
|
+
* };
|
|
91
|
+
* const result = camelCase2SnakeCase(obj);
|
|
92
|
+
* console.log(result); // { foo_bar: 'fooBar', foo_bar2: 'fooBar2', foo_bar3: { foo_bar4: 'fooBar4', foo_bar5: 'fooBar5' } }
|
|
93
|
+
*/ const camelCase2SnakeCase = (obj)=>transformObjectKey(obj, 'CamelToSnake');
|
|
94
|
+
/**
|
|
95
|
+
* convert object key from PascalCase to CamelCase
|
|
96
|
+
* @param obj the object to convert
|
|
97
|
+
* @example
|
|
98
|
+
* const obj = {
|
|
99
|
+
* FooBar: 'fooBar',
|
|
100
|
+
* FooBar2: 'fooBar2',
|
|
101
|
+
* FooBar3: {
|
|
102
|
+
* FooBar4: 'fooBar4',
|
|
103
|
+
* FooBar5: 'fooBar5',
|
|
104
|
+
* },
|
|
105
|
+
* };
|
|
106
|
+
* const result = pascalCase2CamelCase(obj);
|
|
107
|
+
* console.log(result); // { fooBar: 'fooBar', fooBar2: 'fooBar2', fooBar3: { fooBar4: 'fooBar4', fooBar5: 'fooBar5' } }
|
|
108
|
+
*/ const pascalCase2CamelCase = (obj)=>transformObjectKey(obj, 'PascalToCamel');
|
|
109
|
+
/**
|
|
110
|
+
* convert object key from PascalCase to SnakeCase
|
|
111
|
+
* @param obj the object to convert
|
|
112
|
+
* @example
|
|
113
|
+
* const obj = {
|
|
114
|
+
* FooBar: 'fooBar',
|
|
115
|
+
* FooBar2: 'fooBar2',
|
|
116
|
+
* FooBar3: {
|
|
117
|
+
* FooBar4: 'fooBar4',
|
|
118
|
+
* FooBar5: 'fooBar5',
|
|
119
|
+
* },
|
|
120
|
+
* };
|
|
121
|
+
* const result = pascalCase2SnakeCase(obj);
|
|
122
|
+
* console.log(result); // { foo_bar: 'fooBar', foo_bar2: 'fooBar2', foo_bar3: { foo_bar4: 'fooBar4', foo_bar5: 'fooBar5' } }
|
|
123
|
+
*/ const pascalCase2SnakeCase = (obj)=>transformObjectKey(obj, 'PascalToSnake');
|
|
124
|
+
/**
|
|
125
|
+
* convert object key from SnakeCase to CamelCase
|
|
126
|
+
* @param obj the object to convert
|
|
127
|
+
* @example
|
|
128
|
+
* const obj = {
|
|
129
|
+
* foo_bar: 'fooBar',
|
|
130
|
+
* foo_bar2: 'fooBar2',
|
|
131
|
+
* foo_bar3: {
|
|
132
|
+
* foo_bar4: 'fooBar4',
|
|
133
|
+
* foo_bar5: 'fooBar5',
|
|
134
|
+
* },
|
|
135
|
+
* };
|
|
136
|
+
* const result = snakeCase2CamelCase(obj);
|
|
137
|
+
* console.log(result); // { fooBar: 'fooBar', fooBar2: 'fooBar2', fooBar3: { fooBar4: 'fooBar4', fooBar5: 'fooBar5' } }
|
|
138
|
+
*/ const snakeCase2CamelCase = (obj)=>transformObjectKey(obj, 'SnakeToCamel');
|
|
139
|
+
/**
|
|
140
|
+
* convert object key from SnakeCase to PascalCase
|
|
141
|
+
* @param obj the object to convert
|
|
142
|
+
* @example
|
|
143
|
+
* const obj = {
|
|
144
|
+
* foo_bar: 'fooBar',
|
|
145
|
+
* foo_bar2: 'fooBar2',
|
|
146
|
+
* foo_bar3: {
|
|
147
|
+
* foo_bar4: 'fooBar4',
|
|
148
|
+
* foo_bar5: 'fooBar5',
|
|
149
|
+
* },
|
|
150
|
+
* };
|
|
151
|
+
* const result = snakeCase2PascalCase(obj);
|
|
152
|
+
* console.log(result); // { FooBar: 'fooBar', FooBar2: 'fooBar2', FooBar3: { FooBar4: 'fooBar4', FooBar5: 'fooBar5' } }
|
|
153
|
+
*/ const snakeCase2PascalCase = (obj)=>transformObjectKey(obj, 'SnakeToPascal');
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 檢查稅務編號是否符合正確的編號規則。
|
|
157
|
+
*
|
|
158
|
+
* @param {string} taxId - 要檢查的8位數稅務編號。
|
|
159
|
+
* @returns {boolean} - 如果符合規則則返回 true,否則返回 false。
|
|
160
|
+
*
|
|
161
|
+
* ### 編號檢查規則:
|
|
162
|
+
* 1. **基本格式檢查**:
|
|
163
|
+
* - 編號必須為 8 位數字。
|
|
164
|
+
* - 不得為「00000000」或「11111111」,這些編號被視為無效。
|
|
165
|
+
*
|
|
166
|
+
* 2. **驗證邏輯**:
|
|
167
|
+
* - 使用一組驗證運算子:`[1, 2, 1, 2, 1, 2, 4, 1]`。
|
|
168
|
+
* - 將稅務編號的每一位數字與對應的運算子相乘後,使用 `calculate` 函數計算各位數的和。
|
|
169
|
+
* - 計算公式為:`(product % 10) + (product - (product % 10)) / 10`
|
|
170
|
+
* - 將所有位數經計算後的結果加總為 `sum`。
|
|
171
|
+
*
|
|
172
|
+
* 3. **檢查規則**:
|
|
173
|
+
* - 如果總和 `sum` 可以被 5 整除,則編號有效。
|
|
174
|
+
* - 或者,若第七位數為 7,且 `(sum + 1) % 5 === 0`,則編號有效。
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
*
|
|
178
|
+
* validTaxId('22099131') // true
|
|
179
|
+
* validTaxId('84149961') // true
|
|
180
|
+
* validTaxId('00000000') // false
|
|
181
|
+
* validTaxId('11111111') // false
|
|
182
|
+
* validTaxId('22099132') // false
|
|
183
|
+
*/ const validTaxId = (taxId)=>{
|
|
184
|
+
const invalidList = '00000000,11111111';
|
|
185
|
+
if (/^\d{8}$/.test(taxId) === false || invalidList.indexOf(taxId) >= 0) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const validateOperator = [
|
|
189
|
+
1,
|
|
190
|
+
2,
|
|
191
|
+
1,
|
|
192
|
+
2,
|
|
193
|
+
1,
|
|
194
|
+
2,
|
|
195
|
+
4,
|
|
196
|
+
1
|
|
197
|
+
];
|
|
198
|
+
const calculate = (product)=>product % 10 + (product - product % 10) / 10;
|
|
199
|
+
const sum = validateOperator.reduce((pre, cur, index)=>pre + calculate(Number(taxId[index]) * cur), 0);
|
|
200
|
+
return sum % 5 === 0 || taxId[6] === '7' && (sum + 1) % 5 === 0;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* 驗證日期格式是否正確
|
|
204
|
+
*
|
|
205
|
+
* @param dateString 日期字串
|
|
206
|
+
* @param format 日期格式
|
|
207
|
+
* @example
|
|
208
|
+
*
|
|
209
|
+
* validateDateString('20210201', 'YYYYMMDD') // true
|
|
210
|
+
* validateDateString('2021-02-01', 'YYYY-MM-DD') // true
|
|
211
|
+
* validateDateString('20210201', 'YYYY-MM-DD') // false
|
|
212
|
+
* validateDateString('20210201', 'YYYYMM') // false
|
|
213
|
+
*/ const validateDateString = (dateString, format)=>{
|
|
214
|
+
if (!dateString) return false;
|
|
215
|
+
// 根據 format 生成正則表達式
|
|
216
|
+
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}');
|
|
217
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
218
|
+
// 先用正則驗證格式
|
|
219
|
+
if (!regex.test(dateString)) return false;
|
|
220
|
+
// 再用 dayjs 驗證是否為有效日期
|
|
221
|
+
return dayjs(dateString, format, true).isValid();
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 取得去年今年以及明年期別陣列
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* // 假設 今年為 112 年
|
|
229
|
+
* generatePeriodArray() // 11102 ~ 11312
|
|
230
|
+
*/ const generatePeriodArray = ()=>{
|
|
231
|
+
const currentYear = new Date().getFullYear() - 1911;
|
|
232
|
+
const months = [
|
|
233
|
+
'02',
|
|
234
|
+
'04',
|
|
235
|
+
'06',
|
|
236
|
+
'08',
|
|
237
|
+
'10',
|
|
238
|
+
'12'
|
|
239
|
+
];
|
|
240
|
+
const years = [
|
|
241
|
+
currentYear - 1,
|
|
242
|
+
currentYear,
|
|
243
|
+
currentYear + 1
|
|
244
|
+
];
|
|
245
|
+
return years.flatMap((year)=>months.map((month)=>`${year}${month}`));
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* 取得當前期別
|
|
249
|
+
*
|
|
250
|
+
* 報稅期限次期開始15日內
|
|
251
|
+
*
|
|
252
|
+
* 雙數月沒有特殊規則,期別皆為當月開頭
|
|
253
|
+
*
|
|
254
|
+
* 單數月15號以前,期別為上個月否則為下個月開頭
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
*
|
|
258
|
+
* // 假設今天是 111-02-15
|
|
259
|
+
* getCurrentPeriod() // 11102
|
|
260
|
+
* // 假設今天是 111-02-16
|
|
261
|
+
* getCurrentPeriod() // 11102
|
|
262
|
+
* // 假設今天是 111-03-15
|
|
263
|
+
* getCurrentPeriod() // 11102
|
|
264
|
+
* // 假設今天是 111-03-16
|
|
265
|
+
* getCurrentPeriod() // 11104
|
|
266
|
+
*/ const getCurrentPeriod = ()=>{
|
|
267
|
+
const now = dayjs();
|
|
268
|
+
const currentDate = now.format('DD');
|
|
269
|
+
const currentYear = now.format('YYYY');
|
|
270
|
+
const currentMonth = now.format('MM');
|
|
271
|
+
let endMonth;
|
|
272
|
+
if (Number(currentMonth) % 2 === 1) {
|
|
273
|
+
endMonth = dayjs(`${currentYear}${currentMonth}`, 'YYYYMM').add(Number(currentDate) <= 15 ? -1 : 1, 'month').format('YYYYMM');
|
|
274
|
+
} else {
|
|
275
|
+
endMonth = `${currentYear}${currentMonth}`;
|
|
276
|
+
}
|
|
277
|
+
return dayjs(endMonth, 'YYYYMM').subtract(1911, 'year').format('YYYYMM').substring(1);
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* 民國年轉西元年
|
|
281
|
+
* @param dateString 日期字串
|
|
282
|
+
* @param format 日期格式
|
|
283
|
+
* @example
|
|
284
|
+
*
|
|
285
|
+
* rocEraToAd('1100201') // 20210201
|
|
286
|
+
* rocEraToAd('11002', 'YYYYMM') // 202102
|
|
287
|
+
*/ const rocEraToAd = (dateString, format = 'YYYYMMDD')=>{
|
|
288
|
+
if (!validateDateString(`0${dateString}`, format)) return dateString;
|
|
289
|
+
return dayjs(`0${dateString}`, format).add(1911, 'year').format(format);
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* 西元年轉民國年
|
|
293
|
+
* @param dateString 日期字串
|
|
294
|
+
* @param format 日期格式
|
|
295
|
+
* @example
|
|
296
|
+
*
|
|
297
|
+
* adToRocEra('20210201') // 1100201
|
|
298
|
+
* adToRocEra('202102', 'YYYYMM') // 11002
|
|
299
|
+
*/ const adToRocEra = (dateString, format = 'YYYYMMDD')=>{
|
|
300
|
+
if (!validateDateString(dateString, format)) return dateString;
|
|
301
|
+
return dayjs(dateString, format).add(-1911, 'year').format(format).substring(1);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* 檢查檔案是否為合法的 MIME 類型
|
|
306
|
+
*
|
|
307
|
+
* @param file 檔案
|
|
308
|
+
* @param accepts 允許的 MIME 類型
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
*
|
|
312
|
+
* ```js
|
|
313
|
+
* validateFileType({ type: 'image/png' }, ['image/png', 'image/jpeg']) // true
|
|
314
|
+
* validateFileType({ type: 'image/png' }, ['image/jpeg']) // false
|
|
315
|
+
* validateFileType({ type: 'image/png' }, ['image/*']) // true
|
|
316
|
+
* ```
|
|
317
|
+
*/ const validateFileType = (file, accepts)=>{
|
|
318
|
+
if (accepts.length === 0) return true;
|
|
319
|
+
// 獲取文件的MIME類型
|
|
320
|
+
const fileMimeType = file.type;
|
|
321
|
+
return accepts.some((accept)=>{
|
|
322
|
+
if (accept === fileMimeType) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
if (accept.endsWith('/*')) {
|
|
326
|
+
const acceptedCategory = accept.split('/')[0];
|
|
327
|
+
const fileCategory = fileMimeType.split('/')[0];
|
|
328
|
+
if (acceptedCategory === fileCategory) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* 根據檔案副檔名取得對應的 MIME Type
|
|
337
|
+
*
|
|
338
|
+
* 目前支援的副檔名有:pdf, csv, jpeg, jpg, png, zip, txt
|
|
339
|
+
* 除此之外的副檔名皆回傳 application/octet-stream
|
|
340
|
+
*
|
|
341
|
+
* @param fileExtension 檔案副檔名
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
*
|
|
345
|
+
* getMimeType('pdf') // 'application/pdf'
|
|
346
|
+
* getMimeType('csv') // 'text/csv'
|
|
347
|
+
* getMimeType('jpeg') // 'image/jpeg'
|
|
348
|
+
* getMimeType('jpg') // 'image/jpeg'
|
|
349
|
+
* getMimeType('png') // 'image/png'
|
|
350
|
+
* getMimeType('txt') // 'text/plain'
|
|
351
|
+
* getMimeType('zip') // 'application/zip'
|
|
352
|
+
* getMimeType('mp4') // 'application/octet-stream'
|
|
353
|
+
*/ const getMimeType = (fileName)=>{
|
|
354
|
+
switch((fileName.split('.').pop() || '').toLocaleLowerCase()){
|
|
355
|
+
case 'jpeg':
|
|
356
|
+
case 'jpg':
|
|
357
|
+
return 'image/jpeg';
|
|
358
|
+
case 'png':
|
|
359
|
+
return 'image/png';
|
|
360
|
+
case 'pdf':
|
|
361
|
+
return 'application/pdf';
|
|
362
|
+
case 'zip':
|
|
363
|
+
return 'application/zip';
|
|
364
|
+
case 'csv':
|
|
365
|
+
return 'text/csv';
|
|
366
|
+
case 'txt':
|
|
367
|
+
return 'text/plain';
|
|
368
|
+
default:
|
|
369
|
+
return 'application/octet-stream';
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 判斷執行環境是否為 Server(node.js) 端
|
|
375
|
+
*/ const isServer = ()=>typeof window === 'undefined';
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 將物件中的某些 key 排除
|
|
379
|
+
*
|
|
380
|
+
* @param object 原始物件
|
|
381
|
+
* @param keys 要排除的 key
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* const a = { a: 1, b: 2, c: 3, d: 4 };
|
|
385
|
+
* const b = omit(a, 'a', 'b'); // { c: 3, d: 4 }
|
|
386
|
+
*/ const omit = (object, ...keys)=>Object.keys(object).reduce((acc, cur)=>{
|
|
387
|
+
if (keys.includes(cur)) {
|
|
388
|
+
return acc;
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
...acc,
|
|
392
|
+
[cur]: object[cur]
|
|
393
|
+
};
|
|
394
|
+
}, {});
|
|
395
|
+
/**
|
|
396
|
+
* 將物件中的某些 value 排除
|
|
397
|
+
*
|
|
398
|
+
* @param object 原始物件
|
|
399
|
+
* @param values 要排除的 value
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* const a = { a: undefined, b: null, c: 3, d: 4 };
|
|
403
|
+
* const b = omitByValue(a, undefined, null); // { c: 3, d: 4 }
|
|
404
|
+
*/ const omitByValue = (object, ...values)=>Object.entries(object).reduce((acc, [key, value])=>{
|
|
405
|
+
if (values.includes(value)) {
|
|
406
|
+
return acc;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
...acc,
|
|
410
|
+
[key]: value
|
|
411
|
+
};
|
|
412
|
+
}, {});
|
|
413
|
+
/**
|
|
414
|
+
* 將指定格式的物件轉換成類似 enum 的物件
|
|
415
|
+
*
|
|
416
|
+
* @param enumObject enum 物件
|
|
417
|
+
* @param valueKey value 的 key
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
*
|
|
421
|
+
* ```js
|
|
422
|
+
* const myObj = {
|
|
423
|
+
* A: { value: 'a' },
|
|
424
|
+
* B: { value: 'b', other: 'other' },
|
|
425
|
+
* }
|
|
426
|
+
*
|
|
427
|
+
* const enumCode = extractEnumLikeObject(myObj, 'value');
|
|
428
|
+
* console.log(enumCode); // { A: 'a', B: 'b' }
|
|
429
|
+
* ```
|
|
430
|
+
*/ const extractEnumLikeObject = (enumObject, valueKey)=>Object.entries(enumObject).reduce((acc, [key, value])=>({
|
|
431
|
+
...acc,
|
|
432
|
+
[key]: value[valueKey]
|
|
433
|
+
}), {});
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* debounce function
|
|
437
|
+
*
|
|
438
|
+
* @param {Function} fn function to be executed
|
|
439
|
+
* @param {number} delay time to wait before executing the function
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* const debouncedFunction = debounce((a: number, b: number) => console.log(a + b), 1000);
|
|
443
|
+
* debouncedFunction(1, 2);
|
|
444
|
+
* debouncedFunction(3, 4);
|
|
445
|
+
* // after 1 second
|
|
446
|
+
* // 7
|
|
447
|
+
*/ const debounce = (fn, delay)=>{
|
|
448
|
+
let timeout;
|
|
449
|
+
// if (isAsync) {
|
|
450
|
+
// return (...args: P[]) =>
|
|
451
|
+
// new Promise((resolve) => {
|
|
452
|
+
// if (timeout) {
|
|
453
|
+
// clearTimeout(timeout);
|
|
454
|
+
// }
|
|
455
|
+
// timeout = setTimeout(() => {
|
|
456
|
+
// resolve(fn(...args));
|
|
457
|
+
// }, delay);
|
|
458
|
+
// });
|
|
459
|
+
// }
|
|
460
|
+
return (...args)=>{
|
|
461
|
+
if (timeout) {
|
|
462
|
+
clearTimeout(timeout);
|
|
463
|
+
}
|
|
464
|
+
timeout = setTimeout(()=>{
|
|
465
|
+
fn(...args);
|
|
466
|
+
}, delay);
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
/**
|
|
470
|
+
* throttle function
|
|
471
|
+
*
|
|
472
|
+
* @param {Function} fn function to be executed
|
|
473
|
+
* @param {number} delay time to wait before executing the function
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* const throttledFunction = throttle((a: number, b: number) => a + b, 1000);
|
|
477
|
+
* throttledFunction(1, 2); // 3
|
|
478
|
+
* throttledFunction(3, 4); // undefined
|
|
479
|
+
* setTimeout(() => {
|
|
480
|
+
* throttledFunction(5, 6); // 11
|
|
481
|
+
* }, 2000);
|
|
482
|
+
*/ const throttle = (fn, delay)=>{
|
|
483
|
+
let wait = false;
|
|
484
|
+
return (...args)=>{
|
|
485
|
+
if (wait) return undefined;
|
|
486
|
+
const val = fn(...args);
|
|
487
|
+
wait = true;
|
|
488
|
+
setTimeout(()=>{
|
|
489
|
+
wait = false;
|
|
490
|
+
}, delay);
|
|
491
|
+
return val;
|
|
492
|
+
};
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* 中文
|
|
497
|
+
*
|
|
498
|
+
* @param options - 選項
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
*
|
|
502
|
+
* isChinese().test('你好') // true
|
|
503
|
+
* isChinese().test('你 好') // false
|
|
504
|
+
* isChinese({ containSpace: true }).test('你 好') // true
|
|
505
|
+
* isChinese().test('123') // false
|
|
506
|
+
*/ const isChinese = (options)=>new RegExp(`^[\u4e00-\u9fa5${options?.mark ? '\\p{P}' : ''}${options?.containSpace ? '\\s' : ''}]+$`, 'u');
|
|
507
|
+
/**
|
|
508
|
+
* 英文
|
|
509
|
+
*
|
|
510
|
+
* @param options - 選項
|
|
511
|
+
*
|
|
512
|
+
* @example
|
|
513
|
+
*
|
|
514
|
+
* isEnglish().test('abc') // true
|
|
515
|
+
* isEnglish().test('abc def') // false
|
|
516
|
+
* isEnglish({ containSpace: true }).test('abc def') // true
|
|
517
|
+
* isEnglish({ number: true }).test('abc123') // true
|
|
518
|
+
* isEnglish({ mark: true }).test('abc!') // true
|
|
519
|
+
* isEnglish({ fullWidth: true }).test('ABC') // true
|
|
520
|
+
* isEnglish({ lowercase: false }).test('abc') // false
|
|
521
|
+
* isEnglish({ uppercase: false }).test('ABC') // false
|
|
522
|
+
*/ const isEnglish = (options)=>new RegExp(`^[${options?.lowercase ?? true ? 'a-z' : ''}${options?.uppercase ?? true ? 'A-Z' : ''}${options?.number ? '\\d' : ''}${options?.fullWidth ? 'A-Za-z' : ''}${options?.mark ? '\\p{P}\\p{S}' : ''}${options?.containSpace ? '\\s' : ''}]+$`, 'u');
|
|
523
|
+
/**
|
|
524
|
+
* email
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
*
|
|
528
|
+
* isEmail().test('123') // false
|
|
529
|
+
* isEmail().test('123@gmail.com') // true
|
|
530
|
+
*/ const isEmail = ()=>// eslint-disable-next-line
|
|
531
|
+
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
|
|
532
|
+
/**
|
|
533
|
+
* 日期字串
|
|
534
|
+
*
|
|
535
|
+
* @param separator 分隔符號 (預設為 -)
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
*
|
|
539
|
+
* isDateString().test('2021-01-01') // true
|
|
540
|
+
* isDateString('/').test('2021/01/01') // true
|
|
541
|
+
*/ const isDateString = (separator = '-')=>new RegExp(`^\\d{4}${separator}\\d{2}${separator}\\d{2}$`);
|
|
542
|
+
/**
|
|
543
|
+
* 日期時間字串
|
|
544
|
+
*
|
|
545
|
+
* @param separator 分隔符號 (預設為 -)
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
*
|
|
549
|
+
* isDateTimeString().test('2021-01-01 12:00:00') // true
|
|
550
|
+
* isDateTimeString('/').test('2021/01/01 12:00:00') // true
|
|
551
|
+
*/ const isDateTimeString = (separator = '-')=>new RegExp(`^\\d{4}${separator}\\d{2}${separator}\\d{2} \\d{2}:\\d{2}:\\d{2}$`);
|
|
552
|
+
/**
|
|
553
|
+
* 日期時間字串 (不包含豪秒)
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
*
|
|
557
|
+
* isTimeString().test('12:00:00') // true
|
|
558
|
+
* isTimeString().test('12:00:00.123') // false
|
|
559
|
+
*/ const isTimeString = ()=>/^\d{2}:\d{2}:\d{2}$/;
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* 數字
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* isNumber().test('123') // true
|
|
566
|
+
*/ const isNumber = ()=>/^\d+$/;
|
|
567
|
+
/**
|
|
568
|
+
* 非零開頭的數字
|
|
569
|
+
*
|
|
570
|
+
* @param allowZero 是否允許0
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* isNonZeroNumber().test('123') // true
|
|
574
|
+
* isNonZeroNumber().test('0123') // false
|
|
575
|
+
* isNonZeroNumber().test('0') // false
|
|
576
|
+
* isNonZeroNumber(true).test('0') // true
|
|
577
|
+
*/ const isNonZeroStart = (allowZero = false)=>allowZero ? /^[1-9]\d*|0$/ : /^[1-9]\d*$/;
|
|
578
|
+
/**
|
|
579
|
+
* n位數的數字
|
|
580
|
+
*
|
|
581
|
+
* @param n 位數
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* isNumberN(3).test('123') // true
|
|
585
|
+
* isNumberN(3).test('1234') // false
|
|
586
|
+
*/ const isNumberN = (n)=>new RegExp(`^\\d{${n}}$`);
|
|
587
|
+
/**
|
|
588
|
+
* n - m 位數的數字
|
|
589
|
+
*
|
|
590
|
+
* @param n 最小位數
|
|
591
|
+
* @param m 最大位數
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* isNumberNM(3, 5).test('123') // true
|
|
595
|
+
* isNumberNM(3, 5).test('12345') // true
|
|
596
|
+
* isNumberNM(3, 5).test('123456') // false
|
|
597
|
+
*/ const isNumberNM = (n, m)=>new RegExp(`^\\d{${n},${m}}$`);
|
|
598
|
+
/**
|
|
599
|
+
* 至少n位數的數字
|
|
600
|
+
*
|
|
601
|
+
* @param n 最小位數
|
|
602
|
+
*
|
|
603
|
+
* @example
|
|
604
|
+
* isNumberAtLeastN(3).test('123') // true
|
|
605
|
+
* isNumberAtLeastN(3).test('1234') // true
|
|
606
|
+
* isNumberAtLeastN(3).test('12') // false
|
|
607
|
+
*/ const isNumberAtLeastN = (n)=>new RegExp(`^\\d{${n},}$`);
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* 台灣手機號碼
|
|
611
|
+
*
|
|
612
|
+
* 1. 09開頭
|
|
613
|
+
* 2. 10碼數字
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
*
|
|
617
|
+
* isTWMobile().test('0912345678') // true
|
|
618
|
+
* isTWMobile().test('091234567') // false
|
|
619
|
+
*/ const isTWMobile = ()=>/09[0-9]{8}$/;
|
|
620
|
+
/**
|
|
621
|
+
* 台灣市話
|
|
622
|
+
*
|
|
623
|
+
* 1. 0開頭
|
|
624
|
+
* 2. 2~3碼區碼
|
|
625
|
+
* 3. 區碼與號碼之間可以有-號
|
|
626
|
+
* 4. 6~8碼號碼
|
|
627
|
+
* 5. 區碼加上號碼最多10碼
|
|
628
|
+
* 6. 可以有分機號碼,分機號碼為#號開頭,後面接1~5碼數字
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
*
|
|
632
|
+
* isTWPhone().test('0212345678') // true
|
|
633
|
+
* isTWPhone().test('02-12345678') // true
|
|
634
|
+
* isTWPhone().test('023-1234567') // true
|
|
635
|
+
* isTWPhone().test('02-12345678#123') // true
|
|
636
|
+
*
|
|
637
|
+
* isTWPhone().test('02123456789') // false
|
|
638
|
+
* isTWPhone().test('02-123456789') // false
|
|
639
|
+
* isTWPhone().test('02-12345678#123456') // false
|
|
640
|
+
*/ const isTWPhone = ()=>/^(((0[2-9]-?\d{6,8})(#\d{1,5})?)|((0[2-9][0-9]-?\d{6,7})(#\d{1,5})?))$/;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* 將數字轉換成金額千分位格式
|
|
644
|
+
*
|
|
645
|
+
* @param num - 數字
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
*
|
|
649
|
+
* formatAmount(1234567) // '1,234,567'
|
|
650
|
+
*/ const formatAmount = (num)=>{
|
|
651
|
+
if (typeof num !== 'number') return '';
|
|
652
|
+
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
|
653
|
+
};
|
|
654
|
+
/**
|
|
655
|
+
* 將字串的第 n - m 個字轉換成星號
|
|
656
|
+
*
|
|
657
|
+
* @param str - 字串
|
|
658
|
+
* @param n - 起始位置
|
|
659
|
+
* @param m - 結束位置
|
|
660
|
+
*
|
|
661
|
+
* @example
|
|
662
|
+
*
|
|
663
|
+
* formatString('123456', 1, 4) // '1****6'
|
|
664
|
+
*/ const formatStarMask = (str, n, m)=>str.slice(0, n) + '*'.repeat(m - n + 1) + str.slice(m + 1);
|
|
665
|
+
|
|
666
|
+
export { adToRocEra, camelCase2PascalCase, camelCase2SnakeCase, camelString2PascalString, camelString2SnakeString, debounce, extractEnumLikeObject, formatAmount, formatStarMask, generatePeriodArray, getCurrentPeriod, getMimeType, isChinese, isDateString, isDateTimeString, isEmail, isEnglish, isNonZeroStart, isNumber, isNumberAtLeastN, isNumberN, isNumberNM, isServer, isTWMobile, isTWPhone, isTimeString, omit, omitByValue, pascalCase2CamelCase, pascalCase2SnakeCase, pascalString2CamelString, pascalString2SnakeString, rocEraToAd, snakeCase2CamelCase, snakeCase2PascalCase, snakeString2CamelString, snakeString2PascalString, throttle, validTaxId, validateDateString, validateFileType };
|