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