utiller 1.0.412 → 1.0.413
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/index.js +1 -21
- package/lib/exceptioner/ERRORs.js +1 -192
- package/lib/exceptioner/index.js +1 -39
- package/lib/index.js +1 -30
- package/lib/pooller/index.js +1 -1117
- package/lib/utiller/index.js +1 -3531
- package/lib/utiller/nodeutiller.js +3 -1095
- package/package.json +1 -1
- package/template/sample.package.json +1 -1
package/lib/utiller/index.js
CHANGED
|
@@ -1,3531 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
-
Object.defineProperty(exports, "__esModule", {
|
|
5
|
-
value: true
|
|
6
|
-
});
|
|
7
|
-
exports.default = void 0;
|
|
8
|
-
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
-
var _lodash = _interopRequireDefault(require("lodash"));
|
|
10
|
-
var _cryptoJs = _interopRequireDefault(require("crypto-js"));
|
|
11
|
-
var _configerer = require("configerer");
|
|
12
|
-
var _exceptioner = _interopRequireDefault(require("../exceptioner"));
|
|
13
|
-
var _momentTimezone = _interopRequireDefault(require("moment-timezone"));
|
|
14
|
-
var _uuid = require("uuid");
|
|
15
|
-
var _nodeHtmlParser = require("node-html-parser");
|
|
16
|
-
/**
|
|
17
|
-
* npm_module之後每三個月要更新Granular Token
|
|
18
|
-
* 到這裡申請新的token => https://www.npmjs.com/settings/davidtu/tokens/
|
|
19
|
-
* vim ~/.npmrc
|
|
20
|
-
* 更新=> //registry.npmjs.org/:_authToken=${npm_7a9n_xxxxxxxx} <-新token
|
|
21
|
-
* npm publish --dry-run
|
|
22
|
-
* 如果沒報錯 → Token 已生效,可以繼續 deploy。
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
String.format = function () {
|
|
26
|
-
let param = [];
|
|
27
|
-
for (let i = 0, l = arguments.length; i < l; i++) {
|
|
28
|
-
param.push(arguments[i]);
|
|
29
|
-
}
|
|
30
|
-
let statement = param[0]; // get the first element(the original statement)
|
|
31
|
-
param.shift(); // remove the first element from array
|
|
32
|
-
return statement.replace(/\{(\d+)\}/g, function (m, n) {
|
|
33
|
-
return param[n];
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
class Utiller {
|
|
37
|
-
/** Key : idOfSetTimout */
|
|
38
|
-
|
|
39
|
-
/** '1.九.1' => false
|
|
40
|
-
* '1.2.3' => true
|
|
41
|
-
* */
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 刪除物件裡面特別的屬性,預設是刪除value為undefined
|
|
45
|
-
**/
|
|
46
|
-
removeAttributeBy(object, predicate = value => _lodash.default.isUndefined(value)) {
|
|
47
|
-
for (const key in object) {
|
|
48
|
-
if (predicate(object[key])) {
|
|
49
|
-
delete object[key];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
getNumberOfNormalize(value, defaultValue = 0) {
|
|
54
|
-
if (_lodash.default.isNumber(value)) return value;
|
|
55
|
-
try {
|
|
56
|
-
const force = _lodash.default.toNumber(value);
|
|
57
|
-
return _lodash.default.isNumber(force) && !isNaN(force) ? force : defaultValue;
|
|
58
|
-
} catch (error) {
|
|
59
|
-
Util.appendError(`448561684561 ${error.message}`);
|
|
60
|
-
}
|
|
61
|
-
return defaultValue;
|
|
62
|
-
}
|
|
63
|
-
getStringOfNormalize(value, defaultValue = "", trim = false) {
|
|
64
|
-
if (_lodash.default.isString(value)) return trim ? _lodash.default.trim(value) : value;
|
|
65
|
-
try {
|
|
66
|
-
const force = _lodash.default.toString(value);
|
|
67
|
-
return this.isOrEquals(force, "", "undefined") ? defaultValue : trim ? _lodash.default.trim(force) : force;
|
|
68
|
-
} catch (error) {
|
|
69
|
-
Util.appendError(`448616845453 ${error.message}`);
|
|
70
|
-
}
|
|
71
|
-
return defaultValue;
|
|
72
|
-
}
|
|
73
|
-
isValidVersionOfString(versionName) {
|
|
74
|
-
if (this.isUndefinedNullEmpty(versionName)) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
const numbers = versionName.split(".");
|
|
78
|
-
for (const number of numbers) {
|
|
79
|
-
const toNum = _lodash.default.toNumber(number);
|
|
80
|
-
if (!_lodash.default.isNumber(toNum) || isNaN(toNum)) return false;
|
|
81
|
-
}
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
getSeparatorOfUnique() {
|
|
85
|
-
return "།།";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** 1.0.1 => 1.0.2 */
|
|
89
|
-
getStringOfVersionIncrement(stringOfVersion, delta = 1) {
|
|
90
|
-
const numbers = stringOfVersion.split(".").map(each => _lodash.default.toNumber(each));
|
|
91
|
-
const last = numbers.length - 1;
|
|
92
|
-
numbers[last] = numbers[last] + delta;
|
|
93
|
-
return numbers.join(".");
|
|
94
|
-
}
|
|
95
|
-
setLocaleOfMoment(locale = "en") {
|
|
96
|
-
_momentTimezone.default.locale(locale);
|
|
97
|
-
}
|
|
98
|
-
getUuidOfV4() {
|
|
99
|
-
return (0, _uuid.v4)();
|
|
100
|
-
}
|
|
101
|
-
constructor() {
|
|
102
|
-
(0, _defineProperty2.default)(this, "mapOfIdNTimeoutId", {});
|
|
103
|
-
(0, _defineProperty2.default)(this, "getEnvironment", () => {
|
|
104
|
-
return this.env;
|
|
105
|
-
});
|
|
106
|
-
(0, _defineProperty2.default)(this, "isProductionEnvironment", () => {
|
|
107
|
-
return _lodash.default.isEqual(this.getEnvironment(), "prod");
|
|
108
|
-
});
|
|
109
|
-
/** this is used for unit test,
|
|
110
|
-
* param 是給 runInBackground 用的 => param */
|
|
111
|
-
(0, _defineProperty2.default)(this, "asyncUnitTaskFunction", (millionSec = 2000, _funparam = "預設的param", errorSimulator) => async (param = this.getRandomHash(10)) => {
|
|
112
|
-
const randomValue = this.getRandomValue(millionSec, millionSec * 1.2);
|
|
113
|
-
try {
|
|
114
|
-
const symbol = randomValue;
|
|
115
|
-
this.appendInfo(`before executed ===> i'm symbol of ${symbol}, ready to be executed, inner param = ${_funparam}`);
|
|
116
|
-
await this.syncDelay(randomValue);
|
|
117
|
-
if (_lodash.default.isFunction(errorSimulator) && errorSimulator(param)) throw Error("force to made error happen");
|
|
118
|
-
this.appendInfo(`after executed ===> i'm symbol of ${symbol}, the task cost ${randomValue} million-seconds ${param ? `i hav params ===> ${param}` : ""}`);
|
|
119
|
-
return {
|
|
120
|
-
randomValue,
|
|
121
|
-
symbol,
|
|
122
|
-
param
|
|
123
|
-
};
|
|
124
|
-
} catch (error) {
|
|
125
|
-
this.appendError(new Error(`asyncUnitTask() catch error ${error.message}`));
|
|
126
|
-
} finally {
|
|
127
|
-
this.appendInfo(`wow.... finally got you`);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
(0, _defineProperty2.default)(this, "test", word => {
|
|
131
|
-
return async () => {
|
|
132
|
-
await Util.syncDelay(3000);
|
|
133
|
-
console.log("經過了三秒");
|
|
134
|
-
await Util.syncDelay(4000);
|
|
135
|
-
console.log("經過了四秒");
|
|
136
|
-
await Util.syncDelay(5000);
|
|
137
|
-
console.log("經過了五秒");
|
|
138
|
-
await Util.syncDelay(6000);
|
|
139
|
-
console.log("經過了六秒");
|
|
140
|
-
return `3423809432804 ${word}`;
|
|
141
|
-
};
|
|
142
|
-
});
|
|
143
|
-
/** const items = [{ price: 10 }, { price: 120 }, { price: 230 }];
|
|
144
|
-
console.log(findLowestPrice(items)); // 輸出: 10
|
|
145
|
-
*/
|
|
146
|
-
(0, _defineProperty2.default)(this, "findLowestValue", (items, key = "price") => {
|
|
147
|
-
// 提取價格並找出最小值
|
|
148
|
-
const minPrice = _lodash.default.minBy(items, key)[key];
|
|
149
|
-
// 確保回傳的最低價為 integer 型態
|
|
150
|
-
return Math.floor(minPrice);
|
|
151
|
-
});
|
|
152
|
-
/** const items = [{ price: 10 }, { price: 120 }, { price: 230 }];
|
|
153
|
-
console.log(findLowestPrice(items)); // 輸出: 120
|
|
154
|
-
*/
|
|
155
|
-
(0, _defineProperty2.default)(this, "findHighestValue", (items, key = "price") => {
|
|
156
|
-
// 提取價格並找出最小值
|
|
157
|
-
const maxPrice = _lodash.default.maxBy(items, key)[key];
|
|
158
|
-
|
|
159
|
-
// 確保回傳的最低價為 integer 型態
|
|
160
|
-
return Math.floor(maxPrice);
|
|
161
|
-
});
|
|
162
|
-
/**
|
|
163
|
-
* // 測試數據
|
|
164
|
-
* const items = [{ price: 10 }, { price: 120 }, { price: 230 }];
|
|
165
|
-
* console.log(getPriceRange(items)); // 輸出: $10 - $230
|
|
166
|
-
* */
|
|
167
|
-
(0, _defineProperty2.default)(this, "getStringOfValueRange", (items, key = "price", sign = "$") => {
|
|
168
|
-
// 找出最小值和最大值
|
|
169
|
-
const minV = _lodash.default.minBy(items, key)[key];
|
|
170
|
-
const maxV = _lodash.default.maxBy(items, key)[key];
|
|
171
|
-
// 判斷並返回字串
|
|
172
|
-
return maxV === minV ? `$${minV}` : `${sign}${minV} - ${sign}${maxV}`;
|
|
173
|
-
});
|
|
174
|
-
(0, _defineProperty2.default)(this, "getCallersName", () => {
|
|
175
|
-
let callerName;
|
|
176
|
-
try {
|
|
177
|
-
throw new Error();
|
|
178
|
-
} catch (e) {
|
|
179
|
-
let re = /(\w+)@|at (\w+) \(/g,
|
|
180
|
-
st = e.stack,
|
|
181
|
-
m;
|
|
182
|
-
re.exec(st), m = re.exec(st);
|
|
183
|
-
if (!_lodash.default.isNull(m)) callerName = m[1] || m[2];
|
|
184
|
-
}
|
|
185
|
-
if (_lodash.default.startsWith("asyncGeneratorStep", callerName)) callerName = "";
|
|
186
|
-
return callerName;
|
|
187
|
-
});
|
|
188
|
-
(0, _defineProperty2.default)(this, "getRandomValue", (min, max) => {
|
|
189
|
-
min = Math.ceil(min);
|
|
190
|
-
max = Math.floor(max);
|
|
191
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
192
|
-
});
|
|
193
|
-
/**
|
|
194
|
-
* 將items insert指定的index後方
|
|
195
|
-
* modify origin array
|
|
196
|
-
* 如果要insert to head, index 要給 -1
|
|
197
|
-
* const array = [3, 4, 5];
|
|
198
|
-
* utiller.insertToArray(array, -1, 'QQ', 'WW'); /** ['QQ','WW',3,4,5]
|
|
199
|
-
* utiller.insertToArray(array, 1, 'QQ', 'WW'); /** [3,'QQ','WW',4,5]
|
|
200
|
-
* utiller.insertToArray(array, 999, 'QQ', 'WW'); /** [3,4,5,'QQ','WW']
|
|
201
|
-
* */
|
|
202
|
-
(0, _defineProperty2.default)(this, "insertToArray", (array, index, ...items) => {
|
|
203
|
-
if (!Array.isArray(array)) {
|
|
204
|
-
throw new Error("First argument must be an array.");
|
|
205
|
-
}
|
|
206
|
-
// splice 的 index 是插入位置,原函式 index 是插入點的前一個位置
|
|
207
|
-
// index = -1 應插入到開頭 (index 0)
|
|
208
|
-
// index >= array.length 應插入到結尾
|
|
209
|
-
const insertAt = Math.max(0, Math.min(index + 1, array.length));
|
|
210
|
-
array.splice(insertAt, 0, ...items);
|
|
211
|
-
// 注意:此函式直接修改了傳入的 array,行為與原函式不同(原函式隱式返回 undefined 但修改了 array)
|
|
212
|
-
// 如果需要保持原函式返回 undefined 的行為,可以不加 return
|
|
213
|
-
// 如果希望返回修改後的 array,可以 return array;
|
|
214
|
-
});
|
|
215
|
-
/** 西元年 轉成 民國年
|
|
216
|
-
* full = 是否打印出全名 民國 XX 年
|
|
217
|
-
* */
|
|
218
|
-
(0, _defineProperty2.default)(this, "getStringOfYearADConvertToMinguoYear", (gregorianYear, full = false) => {
|
|
219
|
-
const minguoYear = gregorianYear - 1911;
|
|
220
|
-
if (minguoYear > 0) {
|
|
221
|
-
return `${full ? "民國" : ""}${minguoYear}${full ? "年" : ""}`;
|
|
222
|
-
} else {
|
|
223
|
-
return `${full ? "民國" : ""}前${Math.abs(minguoYear)}${full ? "年" : ""}`;
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
/**
|
|
227
|
-
* // Example usage
|
|
228
|
-
* const date = '2024-07-25';
|
|
229
|
-
* console.log(convertDateToTimestamp(date)); // Outputs the timestamp for 2024-07-25*/
|
|
230
|
-
(0, _defineProperty2.default)(this, "convertDateToTimestamp", date => {
|
|
231
|
-
return (0, _momentTimezone.default)(date).valueOf(); // valueOf() returns the timestamp in milliseconds
|
|
232
|
-
});
|
|
233
|
-
/** // 示例函數來測試運行時間
|
|
234
|
-
async function exampleFunction() {
|
|
235
|
-
return new Promise((resolve) => setTimeout(resolve, 3210)); // 模擬3.21秒延遲
|
|
236
|
-
}
|
|
237
|
-
// 測試 measureExecutionTime 函數
|
|
238
|
-
measureExecutionTime(exampleFunction).then(result => {
|
|
239
|
-
console.log(result); // 輸出:0小時 0分 3.210秒 (合計 3.210 秒)
|
|
240
|
-
});
|
|
241
|
-
*/
|
|
242
|
-
(0, _defineProperty2.default)(this, "measureExecutionTime", async (fn, ...param) => {
|
|
243
|
-
// 開始計時
|
|
244
|
-
const startTime = Date.now();
|
|
245
|
-
|
|
246
|
-
// 執行傳入的函數
|
|
247
|
-
await fn(...param);
|
|
248
|
-
|
|
249
|
-
// 結束計時
|
|
250
|
-
const endTime = Date.now();
|
|
251
|
-
|
|
252
|
-
// 計算總運行時間(毫秒)
|
|
253
|
-
const durationInMilliseconds = endTime - startTime;
|
|
254
|
-
|
|
255
|
-
// 使用 moment.duration 格式化為 hh:mm:ss.SSS
|
|
256
|
-
const duration = _momentTimezone.default.duration(durationInMilliseconds, "milliseconds");
|
|
257
|
-
const hours = Math.floor(duration.asHours());
|
|
258
|
-
const minutes = duration.minutes();
|
|
259
|
-
const seconds = duration.seconds();
|
|
260
|
-
const milliseconds = duration.milliseconds();
|
|
261
|
-
|
|
262
|
-
// 計算總秒數(包含小數點)
|
|
263
|
-
const totalSeconds = (durationInMilliseconds / 1000).toFixed(3);
|
|
264
|
-
|
|
265
|
-
// 返回結果
|
|
266
|
-
this.appendInfo(`${hours}小時 ${minutes}分 ${seconds}.${milliseconds.toString().padStart(3, "0")}秒 (合計 ${totalSeconds} 秒)`);
|
|
267
|
-
});
|
|
268
|
-
(0, _defineProperty2.default)(this, "formatPriceWithCurrency", (number, locale) => {
|
|
269
|
-
if (typeof number !== "number" || typeof locale !== "string") {
|
|
270
|
-
throw new TypeError("Invalid input: number must be a number and locale must be a string.");
|
|
271
|
-
}
|
|
272
|
-
return new Intl.NumberFormat(locale, {
|
|
273
|
-
style: "currency",
|
|
274
|
-
currency: new Intl.Locale(locale).maximize().currency || "USD",
|
|
275
|
-
minimumFractionDigits: 0 // 確保不顯示小數
|
|
276
|
-
}).format(number);
|
|
277
|
-
});
|
|
278
|
-
(0, _defineProperty2.default)(this, "formatPrice", (number, locale) => {
|
|
279
|
-
if (typeof number !== "number") {
|
|
280
|
-
throw new TypeError("Invalid input: number must be a number.");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// 如果沒有傳入 locale,僅格式化數字
|
|
284
|
-
if (!locale) {
|
|
285
|
-
return number.toLocaleString("en-US"); // 預設使用美式數字格式
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// 有傳入 locale,使用貨幣格式化
|
|
289
|
-
return new Intl.NumberFormat(locale, {
|
|
290
|
-
style: "currency",
|
|
291
|
-
currency: new Intl.Locale(locale).maximize().currency || "USD",
|
|
292
|
-
minimumFractionDigits: 0 // 確保不顯示小數
|
|
293
|
-
}).format(number);
|
|
294
|
-
});
|
|
295
|
-
/**
|
|
296
|
-
* 使用 ES10+ 和 Lodash,將短句子轉換為潛在的搜尋關鍵字陣列。
|
|
297
|
-
* 策略:1. 提取英文單字 2. 提取規格/色號詞組 3. 清理中文 4. 窮舉中文 N-gram 5. 去重和過濾
|
|
298
|
-
* @param {string} sentence - 原始字串。
|
|
299
|
-
* @param {number} [maxLength=50] - 允許的最大字串長度。
|
|
300
|
-
* @param {number} [maxNgramLength=3] - N-gram 的最大長度,預設為 3。
|
|
301
|
-
* @returns {string[]} - 潛在關鍵字陣列(至少 2 個字/中文字)。
|
|
302
|
-
*/
|
|
303
|
-
(0, _defineProperty2.default)(this, "generateUniversalKeywords", (sentence, maxLength = 50, maxNgramLength = 4) => {
|
|
304
|
-
// 引入 Lodash,確保在執行環境中可用
|
|
305
|
-
if (!sentence || typeof sentence !== "string") {
|
|
306
|
-
return [];
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// 假設 Lodash 已經全域引入為 '_'
|
|
310
|
-
if (typeof _lodash.default === "undefined") {
|
|
311
|
-
console.error("Lodash is not available. Please ensure it is imported.");
|
|
312
|
-
return [];
|
|
313
|
-
}
|
|
314
|
-
let textToProcess = sentence.trim();
|
|
315
|
-
let keywords = []; // 最終所有關鍵字的集合
|
|
316
|
-
|
|
317
|
-
// 參數安全檢查 (確保 N-gram 長度至少為 2)
|
|
318
|
-
maxNgramLength = Math.max(2, maxNgramLength);
|
|
319
|
-
|
|
320
|
-
// 警告/截斷邏輯
|
|
321
|
-
if (textToProcess.length > maxLength) {
|
|
322
|
-
console.warn(`警告:輸入字串長度為 ${textToProcess.length},已根據 maxLength: ${maxLength} 截斷。`);
|
|
323
|
-
textToProcess = textToProcess.substring(0, maxLength);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// --- 步驟 1: 提取完整英文單字 (SACHIA, sachia, Sachia) ---
|
|
327
|
-
const englishWords = textToProcess.match(/[a-zA-Z]+/g) || [];
|
|
328
|
-
englishWords.forEach(word => {
|
|
329
|
-
if (word.length >= 2) {
|
|
330
|
-
keywords.push(word.toUpperCase());
|
|
331
|
-
keywords.push(word.toLowerCase());
|
|
332
|
-
const capitalized = word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
333
|
-
keywords.push(capitalized);
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// --- 步驟 2: 提取規格、數量和色號關鍵字 (12W, 10ml, 10色, 1號) ---
|
|
338
|
-
const specKeywordsRegex = /\b[0-9]+[a-zA-Z]{1,4}\b|\b[0-9]{1,3}(w|ml|g|oz|k)\b|[\u4e00-\u9fa5a-zA-Z0-9]{1,4}(色|號)[\u4e00-\u9fa5a-zA-Z0-9]{0,2}/gi;
|
|
339
|
-
const specKeywords = (textToProcess.match(specKeywordsRegex) || []).filter(k => k.length >= 2).map(k => k.toLowerCase());
|
|
340
|
-
keywords.push(...specKeywords);
|
|
341
|
-
|
|
342
|
-
// --- 步驟 3: 清理和標準化 (用於 N-gram 提取) ---
|
|
343
|
-
|
|
344
|
-
let cleanText = textToProcess.toLowerCase();
|
|
345
|
-
|
|
346
|
-
// 1. 將所有提取過的規格詞彙(數字+單位/字母)替換為空格
|
|
347
|
-
cleanText = cleanText.replace(/[0-9]+([\u4e00-\u9fa5a-z]{1,4}|[\/\-\~\\])/g, " ").replace(/\b[a-z]{1,4}\b/g, " ").replace(/\b[0-9]{1,3}\b/g, " ");
|
|
348
|
-
|
|
349
|
-
// 2. 移除通用詞和符號
|
|
350
|
-
cleanText = cleanText.replace(/[!@#$%^&*()_+={}\[\]:;"'<>,.?\/\\|`~]/g, " ").replace(/系列|一組|單色|多款|套組|全套|專用|迷你|頂級|高品質|超閃|奢華|最新|款式|新款|超亮|的|與|和|閃|美甲/g, " ").replace(/\s+/g, "") // **移除所有空格**
|
|
351
|
-
.trim();
|
|
352
|
-
|
|
353
|
-
// --- 步驟 4: 窮舉所有 >= 2 個字的 N-gram 詞組 (針對純中文) ---
|
|
354
|
-
const minLength = 2;
|
|
355
|
-
// maxNgramLength 現在是從參數傳入的
|
|
356
|
-
|
|
357
|
-
for (let len = minLength; len <= maxNgramLength; len++) {
|
|
358
|
-
for (let i = 0; i <= cleanText.length - len; i++) {
|
|
359
|
-
const keyword = cleanText.substring(i, i + len);
|
|
360
|
-
keywords.push(keyword);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// --- 步驟 5: 標準化、去重和過濾 ---
|
|
365
|
-
const finalKeywords = _lodash.default.chain(keywords).filter(k => k.length >= 2).filter(k => k.length > 2 || !/^[\u4e00-\u9fa5a-z0-9]$/.test(k)).uniq().sortBy().value();
|
|
366
|
-
return finalKeywords;
|
|
367
|
-
});
|
|
368
|
-
/**
|
|
369
|
-
* 直接修改原本 array,將 object 移動到指定 index
|
|
370
|
-
* @param {Array} array - 要修改的陣列
|
|
371
|
-
* @param {Object} object - 要移動的物件
|
|
372
|
-
* @param {number} index - 目標位置(預設為 0)
|
|
373
|
-
* @returns {Array} 修改後的原陣列(in-place)
|
|
374
|
-
*/
|
|
375
|
-
(0, _defineProperty2.default)(this, "mutateIndexOfArrayItem", (array, object, index = 0) => {
|
|
376
|
-
if (!Array.isArray(array) || !_lodash.default.isObject(object)) return array;
|
|
377
|
-
const currentIndex = _lodash.default.findIndex(array, item => _lodash.default.isEqual(item, object));
|
|
378
|
-
if (currentIndex === -1) return array; // 找不到物件,直接回傳
|
|
379
|
-
|
|
380
|
-
array.splice(currentIndex, 1); // 先移除原位置
|
|
381
|
-
const targetIndex = _lodash.default.clamp(index, 0, array.length);
|
|
382
|
-
array.splice(targetIndex, 0, object); // 插入到新位置
|
|
383
|
-
|
|
384
|
-
return array;
|
|
385
|
-
});
|
|
386
|
-
/**
|
|
387
|
-
* 將指定 object 移動到 array 中的指定位置
|
|
388
|
-
* @param {Array} array - 要操作的陣列
|
|
389
|
-
* @param {Object} object - 要移動的目標物件
|
|
390
|
-
* @param {number} index - 新的位置(預設為 0)
|
|
391
|
-
* @returns {Array} 新的陣列
|
|
392
|
-
*/
|
|
393
|
-
(0, _defineProperty2.default)(this, "getArrayOfModifyObject2Index", (array, object, index = 0) => {
|
|
394
|
-
if (!Array.isArray(array) || !_lodash.default.isObject(object)) return array;
|
|
395
|
-
const cloned = _lodash.default.cloneDeep(array); // 深拷貝以避免原陣列被 mutate
|
|
396
|
-
const currentIndex = _lodash.default.findIndex(cloned, item => _lodash.default.isEqual(item, object));
|
|
397
|
-
if (currentIndex === -1) return array; // 沒找到物件就回傳原陣列
|
|
398
|
-
|
|
399
|
-
// 移除原來位置
|
|
400
|
-
cloned.splice(currentIndex, 1);
|
|
401
|
-
|
|
402
|
-
// 插入到指定位置(修正越界 index)
|
|
403
|
-
const targetIndex = _lodash.default.clamp(index, 0, cloned.length);
|
|
404
|
-
cloned.splice(targetIndex, 0, object);
|
|
405
|
-
return cloned;
|
|
406
|
-
});
|
|
407
|
-
/**
|
|
408
|
-
* const origin = [
|
|
409
|
-
* { label: 'aa', value: 1203 },
|
|
410
|
-
* { label: 'cc', value: 1204 },
|
|
411
|
-
* { label: 'gg', value: 2 }
|
|
412
|
-
* ];
|
|
413
|
-
*
|
|
414
|
-
* const latest = ['aa', 'bb', 'aa', 'dd'];
|
|
415
|
-
* console.log(generateLabelValuePairsWithOrigin(origin, latest));
|
|
416
|
-
* [
|
|
417
|
-
* { label: 'aa', value: 1203 }, // 來自 origin
|
|
418
|
-
* { label: 'bb', value: 843910 }, // 隨機唯一值
|
|
419
|
-
* { label: 'dd', value: 692384 } // 隨機唯一值
|
|
420
|
-
* ]
|
|
421
|
-
*
|
|
422
|
-
* 根據 latest 字串陣列建立 label/value 物件陣列。
|
|
423
|
-
* 若 label 存在於 origin 中,則沿用 origin 的 value;
|
|
424
|
-
* 若 label 不存在,則產生唯一隨機數值作為 value(value 不可重複)。
|
|
425
|
-
*
|
|
426
|
-
* @param {Array<{label: string, value: number}>} origin - 原始資料來源,包含已知 label 與對應的 value。
|
|
427
|
-
* @param {Array<string>} latest - 最新輸入的 label 陣列,可能有重複或新值。
|
|
428
|
-
* @returns {Array<{label: string, value: number}>} - 轉換完成的唯一 label/value 陣列。
|
|
429
|
-
*/
|
|
430
|
-
(0, _defineProperty2.default)(this, "generateLabelValuePairsWithOrigin", (origin = [{
|
|
431
|
-
label: "aa",
|
|
432
|
-
value: 1203
|
|
433
|
-
}, {
|
|
434
|
-
label: "cc",
|
|
435
|
-
value: 1204
|
|
436
|
-
}, {
|
|
437
|
-
label: "gg",
|
|
438
|
-
value: 2
|
|
439
|
-
}], latest = ["aa", "bb"]) => {
|
|
440
|
-
// 建立已使用過的 value 集合,避免重複
|
|
441
|
-
const usedValues = new Set(origin.map(o => o.value));
|
|
442
|
-
|
|
443
|
-
// 處理 latest label 清單
|
|
444
|
-
return _lodash.default.chain(latest).uniq() // 1. 移除重複的 label(只保留唯一值)
|
|
445
|
-
.map(label => {
|
|
446
|
-
// 2. 嘗試從 origin 找出是否已存在該 label
|
|
447
|
-
const originItem = _lodash.default.find(origin, {
|
|
448
|
-
label
|
|
449
|
-
});
|
|
450
|
-
if (originItem) {
|
|
451
|
-
// 3. 若存在,直接使用 origin 中的 value
|
|
452
|
-
return {
|
|
453
|
-
label,
|
|
454
|
-
value: originItem.value
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// 4. 若不存在,產生一個不重複的隨機 value
|
|
459
|
-
let value;
|
|
460
|
-
do {
|
|
461
|
-
// Firestore 可接受的整數範圍(可調整範圍)
|
|
462
|
-
value = _lodash.default.random(2, 999999999);
|
|
463
|
-
} while (usedValues.has(value)); // 確保 value 唯一
|
|
464
|
-
|
|
465
|
-
// 5. 記錄該值為已使用,避免後續重複
|
|
466
|
-
usedValues.add(value);
|
|
467
|
-
|
|
468
|
-
// 6. 回傳新的物件
|
|
469
|
-
return {
|
|
470
|
-
label,
|
|
471
|
-
value
|
|
472
|
-
};
|
|
473
|
-
}).value(); // 7. 輸出處理完成的物件陣列
|
|
474
|
-
});
|
|
475
|
-
/**
|
|
476
|
-
* 比對 values 是否出現在 sourceArray 的指定欄位中,並標記 flagKey 欄位
|
|
477
|
-
*
|
|
478
|
-
* @param {Array} values - 要比對的值陣列,例如: [1, 3]
|
|
479
|
-
* @param {Array} sourceArray - 要處理的物件陣列
|
|
480
|
-
* @param {string} [valueKey='value'] - 要比對的欄位名稱,預設為 'value'
|
|
481
|
-
* @param {string} [flagKey='belong'] - 回傳中標記的欄位名稱,預設為 'belong'
|
|
482
|
-
* @returns {Array} 處理後的陣列
|
|
483
|
-
* const B = [
|
|
484
|
-
* { value: 1, label: 'hi' },
|
|
485
|
-
* { value: 2, label: 'hii' },
|
|
486
|
-
* { value: 3, label: 'hiii' }
|
|
487
|
-
* ];
|
|
488
|
-
* console.log(utiller.getItemsOfMarkMatching( B,[1]))
|
|
489
|
-
* [
|
|
490
|
-
* { value: 1, label: 'hi', belong: true },
|
|
491
|
-
* { value: 2, label: 'hii', belong: false },
|
|
492
|
-
* { value: 3, label: 'hiii', belong: false }
|
|
493
|
-
* ]
|
|
494
|
-
**/
|
|
495
|
-
(0, _defineProperty2.default)(this, "getItemsOfMarkMatching", (sourceArray = [], values = [], valueKey = "value", flagKey = "belong") => {
|
|
496
|
-
const valuesSet = new Set(values); // 使用 Set 提高效能
|
|
497
|
-
return _lodash.default.map(sourceArray, item => ({
|
|
498
|
-
...item,
|
|
499
|
-
[flagKey]: valuesSet.has(item[valueKey])
|
|
500
|
-
}));
|
|
501
|
-
});
|
|
502
|
-
/**
|
|
503
|
-
* 將多維屬性陣列進行排列組合,輸出為組合 label 和 value。
|
|
504
|
-
* @param {Array<Array<{label: string, value: string}>>} arrays - 多個陣列,每個陣列包含 {label, value}
|
|
505
|
-
* @param {string} labelSeparator - 標籤用的分隔符號(預設為 '|')
|
|
506
|
-
* @param {string} valueSeparator - 值用的分隔符號(預設為 '-')
|
|
507
|
-
* @returns {Array<{label: string, value: string}>}
|
|
508
|
-
*
|
|
509
|
-
* const arrays = [
|
|
510
|
-
* [
|
|
511
|
-
* { label: '紅', value: '1b' },
|
|
512
|
-
* { label: '黑', value: 'ca' }
|
|
513
|
-
* ],
|
|
514
|
-
* [
|
|
515
|
-
* { label: 'M號', value: 'f2' },
|
|
516
|
-
* { label: 'L號', value: 'q5' }
|
|
517
|
-
* ],
|
|
518
|
-
* [
|
|
519
|
-
* { label: '短袖', value: 's1' },
|
|
520
|
-
* { label: '長袖', value: 's2' }
|
|
521
|
-
* ]
|
|
522
|
-
* ];
|
|
523
|
-
* output
|
|
524
|
-
* [
|
|
525
|
-
* { label: '紅|M號|短袖', value: '1b-f2-s1' },
|
|
526
|
-
* { label: '紅|M號|長袖', value: '1b-f2-s2' },
|
|
527
|
-
* { label: '紅|L號|短袖', value: '1b-q5-s1' },
|
|
528
|
-
* { label: '紅|L號|長袖', value: '1b-q5-s2' },
|
|
529
|
-
* { label: '黑|M號|短袖', value: 'ca-f2-s1' },
|
|
530
|
-
* { label: '黑|M號|長袖', value: 'ca-f2-s2' },
|
|
531
|
-
* { label: '黑|L號|短袖', value: 'ca-q5-s1' },
|
|
532
|
-
* { label: '黑|L號|長袖', value: 'ca-q5-s2' }
|
|
533
|
-
* ] *
|
|
534
|
-
*/
|
|
535
|
-
/**
|
|
536
|
-
* 產生排列組合(容忍空陣列,將非空單一陣列視為結果)
|
|
537
|
-
* @param {Array<Array<{label: string, value: string}>>} arrays
|
|
538
|
-
* @param {string} labelSeparator
|
|
539
|
-
* @param {string} valueSeparator
|
|
540
|
-
* @returns {Array<{label: string, value: string}>}
|
|
541
|
-
*/
|
|
542
|
-
(0, _defineProperty2.default)(this, "generateVariants", (arrays, labelSeparator = "|", valueSeparator = "-") => {
|
|
543
|
-
// 過濾掉空陣列
|
|
544
|
-
const nonEmptyArrays = arrays.filter(arr => arr.length > 0);
|
|
545
|
-
if (nonEmptyArrays.length === 0) return [];
|
|
546
|
-
if (nonEmptyArrays.length === 1) {
|
|
547
|
-
// 若只有一個非空陣列,回傳它(每項轉為 {label, value} 格式)
|
|
548
|
-
return nonEmptyArrays[0].map(item => ({
|
|
549
|
-
label: item.label,
|
|
550
|
-
value: item.value
|
|
551
|
-
}));
|
|
552
|
-
}
|
|
553
|
-
const combinations = _lodash.default.reduce(nonEmptyArrays, (acc, curr) => _lodash.default.flatMap(acc, a => curr.map(b => [...a, b])), [[]]);
|
|
554
|
-
return combinations.map(comb => ({
|
|
555
|
-
label: comb.map(item => item.label).join(labelSeparator),
|
|
556
|
-
value: comb.map(item => item.value).join(valueSeparator)
|
|
557
|
-
}));
|
|
558
|
-
});
|
|
559
|
-
/**
|
|
560
|
-
* 對物件陣列中的 key 進行重新命名
|
|
561
|
-
* @param {Array<Object>} arr - 原始資料陣列
|
|
562
|
-
* @param {...[string, string]} keyMappings - key 對應對照,例如 ['label', 'labelOfVariant']
|
|
563
|
-
* @returns {Array<Object>}
|
|
564
|
-
*
|
|
565
|
-
* const originalVariants = [
|
|
566
|
-
* { label: '紅|M號', value: '1b-f2' },
|
|
567
|
-
* { label: '紅|L號', value: '1b-q5' }
|
|
568
|
-
* ];
|
|
569
|
-
*
|
|
570
|
-
* renameKeysInArray(
|
|
571
|
-
* originalVariants,
|
|
572
|
-
* ['label', 'labelOfVariant'],
|
|
573
|
-
* ['value', 'valueOfVariant']
|
|
574
|
-
* );
|
|
575
|
-
*
|
|
576
|
-
* outputs:
|
|
577
|
-
* [
|
|
578
|
-
* { labelOfVariant: '紅|M號', valueOfVariant: '1b-f2' },
|
|
579
|
-
* { labelOfVariant: '紅|L號', valueOfVariant: '1b-q5' }
|
|
580
|
-
* ]
|
|
581
|
-
*
|
|
582
|
-
*/
|
|
583
|
-
(0, _defineProperty2.default)(this, "renameKeysInArray", (arr, ...keyMappings) => {
|
|
584
|
-
const mapping = Object.fromEntries(keyMappings);
|
|
585
|
-
return arr.map(item => _lodash.default.mapKeys(item, (value, key) => mapping[key] || key));
|
|
586
|
-
});
|
|
587
|
-
/**
|
|
588
|
-
* 將 array2 的對應項目合併到 array1 中(支援巢狀 idKey 路徑)
|
|
589
|
-
* @param {Array<Object>} array1
|
|
590
|
-
* @param {Array<Object>} array2
|
|
591
|
-
* @param {string} idKey - 用來比對的 key(可為巢狀路徑,例如 'meta.id')
|
|
592
|
-
* @returns {Array<Object>} - 合併後的 array1
|
|
593
|
-
*
|
|
594
|
-
* const array1 = [
|
|
595
|
-
* { meta: { id: 'a1' }, name: 'Red' },
|
|
596
|
-
* { meta: { id: 'b2' }, name: 'Black' }
|
|
597
|
-
* ];
|
|
598
|
-
*
|
|
599
|
-
* const array2 = [
|
|
600
|
-
* { meta: { id: 'a1' }, price: 200 },
|
|
601
|
-
* { meta: { id: 'b2' }, name: 'Black Special' }
|
|
602
|
-
* ];
|
|
603
|
-
*
|
|
604
|
-
* const result = mergeById(array1, array2, 'meta.id');
|
|
605
|
-
* console.log(result);
|
|
606
|
-
*/
|
|
607
|
-
(0, _defineProperty2.default)(this, "getArrayOfMergeBySpecificId", (array1, array2, idKey = "id") => {
|
|
608
|
-
if (!Array.isArray(array2)) return array1;
|
|
609
|
-
const map2 = _lodash.default.keyBy(array2, item => _lodash.default.get(item, idKey));
|
|
610
|
-
return array1.map(item => {
|
|
611
|
-
const id = _lodash.default.get(item, idKey);
|
|
612
|
-
const match = map2[id];
|
|
613
|
-
return match ? _lodash.default.merge({}, item, match) : item;
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
/**
|
|
617
|
-
* 將百分比數值轉換為小數形式。
|
|
618
|
-
*
|
|
619
|
-
* @example
|
|
620
|
-
* toPercentageDecimal(100); // 1
|
|
621
|
-
* toPercentageDecimal(97); // 0.97
|
|
622
|
-
* toPercentageDecimal('97'); // 0.97
|
|
623
|
-
* toPercentageDecimal('97%'); // 0.97
|
|
624
|
-
* toPercentageDecimal('10 %'); // 0.1
|
|
625
|
-
* toPercentageDecimal('abc'); // 1 (非數字字串時,回傳 1)
|
|
626
|
-
*
|
|
627
|
-
* @param {number|string} num - 百分比值,可包含 "%" 符號或字串格式。
|
|
628
|
-
* @returns {number} - 轉換後的小數值,若轉換失敗則回傳 1。
|
|
629
|
-
*/
|
|
630
|
-
(0, _defineProperty2.default)(this, "toPercentageDecimal", num => {
|
|
631
|
-
if (_lodash.default.isNil(num)) return 1;
|
|
632
|
-
|
|
633
|
-
// 若輸入為字串,先移除 "%" 符號與多餘空白
|
|
634
|
-
if (_lodash.default.isString(num)) {
|
|
635
|
-
num = num.replace(/%/g, "").trim();
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// 轉換為數字
|
|
639
|
-
const parsed = _lodash.default.toNumber(num);
|
|
640
|
-
|
|
641
|
-
// 若不是有限數字,回傳預設值 1
|
|
642
|
-
if (!_lodash.default.isFinite(parsed)) return 1;
|
|
643
|
-
|
|
644
|
-
// 四捨五入到小數點第 10 位
|
|
645
|
-
return _lodash.default.round(parsed / 100, 10);
|
|
646
|
-
});
|
|
647
|
-
/**
|
|
648
|
-
* 無條件進位的公式
|
|
649
|
-
* multiplyCeil(1.24, 3); // => 4
|
|
650
|
-
* multiplyCeil(1.24, 3, 1); // => 3.8
|
|
651
|
-
* multiplyCeil(1.24, 3, 2); // => 3.72
|
|
652
|
-
*/
|
|
653
|
-
(0, _defineProperty2.default)(this, "getNumberOfMultiplyCeil", (a, b, precision = 0) => {
|
|
654
|
-
const factor = Math.pow(10, precision);
|
|
655
|
-
return Math.ceil(a * b * factor) / factor;
|
|
656
|
-
});
|
|
657
|
-
/**
|
|
658
|
-
* 根據課程資訊生成 Google Calendar 的行事曆新增連結。
|
|
659
|
-
* 若部分參數為空值,則不加入連結中。
|
|
660
|
-
*
|
|
661
|
-
* @param {object} event - 行程物件。
|
|
662
|
-
* @param {string} event.title - 行程主題 (e.g., '吉他課')。
|
|
663
|
-
* @param {string} event.startDate - 開始日期 (e.g., '2025/11/10')。
|
|
664
|
-
* @param {string} event.startTime - 開始時間 (e.g., '14:00')。
|
|
665
|
-
* @param {string} event.endDate - 結束日期 (e.g., '2025/11/10')。
|
|
666
|
-
* @param {string} event.endTime - 結束時間 (e.g., '16:00')。
|
|
667
|
-
* @param {string} [event.location] - 地點/地址。
|
|
668
|
-
* @param {string} [event.details] - 描述/備註。
|
|
669
|
-
* @returns {string} Google Calendar 的新增連結。
|
|
670
|
-
*/
|
|
671
|
-
(0, _defineProperty2.default)(this, "generateGoogleCalendarLink", ({
|
|
672
|
-
title,
|
|
673
|
-
startDate,
|
|
674
|
-
startTime,
|
|
675
|
-
endDate,
|
|
676
|
-
endTime,
|
|
677
|
-
location,
|
|
678
|
-
details
|
|
679
|
-
}) => {
|
|
680
|
-
const startDateTime = (0, _momentTimezone.default)(`${startDate} ${startTime}`, "YYYY/MM/DD HH:mm").format("YYYYMMDDTHHmmss");
|
|
681
|
-
const endDateTime = (0, _momentTimezone.default)(`${endDate} ${endTime}`, "YYYY/MM/DD HH:mm").format("YYYYMMDDTHHmmss");
|
|
682
|
-
const params = new URLSearchParams();
|
|
683
|
-
if (title) params.append("text", title);
|
|
684
|
-
if (startDateTime && endDateTime) params.append("dates", `${startDateTime}/${endDateTime}`);
|
|
685
|
-
if (details) params.append("details", details);
|
|
686
|
-
if (location) params.append("location", location);
|
|
687
|
-
params.append("ctz", "Asia/Taipei");
|
|
688
|
-
params.append("trp", "true");
|
|
689
|
-
return `https://calendar.google.com/calendar/r/eventedit?${params.toString()}`;
|
|
690
|
-
});
|
|
691
|
-
/**
|
|
692
|
-
* 根據課程資訊生成 TimeTree 的行事曆新增連結。
|
|
693
|
-
* 若部分參數為空值,則不加入連結中。
|
|
694
|
-
*
|
|
695
|
-
* @param {object} event - 行程物件。
|
|
696
|
-
* @param {string} event.title - 行程主題。
|
|
697
|
-
* @param {string} event.startDate - 開始日期 (YYYY/MM/DD)。
|
|
698
|
-
* @param {string} event.startTime - 開始時間 (HH:MM)。
|
|
699
|
-
* @param {string} event.endDate - 結束日期 (YYYY/MM/DD)。
|
|
700
|
-
* @param {string} event.endTime - 結束時間 (HH:MM)。
|
|
701
|
-
* @param {string} [event.location] - 地點/地址。
|
|
702
|
-
* @param {string} [event.memo] - 描述/備註 (TimeTree 使用 'memo')。
|
|
703
|
-
* @returns {string} TimeTree 行事曆新增連結。
|
|
704
|
-
*/
|
|
705
|
-
(0, _defineProperty2.default)(this, "generateTimeTreeLink", ({
|
|
706
|
-
title,
|
|
707
|
-
startDate,
|
|
708
|
-
startTime,
|
|
709
|
-
endDate,
|
|
710
|
-
endTime,
|
|
711
|
-
location,
|
|
712
|
-
memo
|
|
713
|
-
}) => {
|
|
714
|
-
const params = new URLSearchParams();
|
|
715
|
-
if (title) params.append("title", title);
|
|
716
|
-
if (startDate) params.append("start_date", startDate.replace(/\//g, "-"));
|
|
717
|
-
if (startTime) params.append("start_time", startTime);
|
|
718
|
-
if (endDate) params.append("end_date", endDate.replace(/\//g, "-"));
|
|
719
|
-
if (endTime) params.append("end_time", endTime);
|
|
720
|
-
if (location) params.append("location", location);
|
|
721
|
-
if (memo) params.append("memo", memo);
|
|
722
|
-
return `https://timetreeapp.com/plans/new?${params.toString()}`;
|
|
723
|
-
});
|
|
724
|
-
/**
|
|
725
|
-
* 根據課程資訊生成 iCalendar (.ics) 檔案內容。
|
|
726
|
-
* 若部分欄位為空值,則不寫入該欄位。
|
|
727
|
-
*
|
|
728
|
-
* @param {object} event - 行程物件。
|
|
729
|
-
* @param {string} event.title - 行程主題。
|
|
730
|
-
* @param {string} event.startDate - 開始日期 (YYYY/MM/DD)。
|
|
731
|
-
* @param {string} event.startTime - 開始時間 (HH:MM)。
|
|
732
|
-
* @param {string} event.endDate - 結束日期 (YYYY/MM/DD)。
|
|
733
|
-
* @param {string} event.endTime - 結束時間 (HH:MM)。
|
|
734
|
-
* @param {string} [event.location] - 地點/地址。
|
|
735
|
-
* @param {string} [event.details] - 描述/備註。
|
|
736
|
-
* @returns {string} 遵循 iCalendar 規範的字串內容。
|
|
737
|
-
*/
|
|
738
|
-
(0, _defineProperty2.default)(this, "generateIcsContent", ({
|
|
739
|
-
title,
|
|
740
|
-
startDate,
|
|
741
|
-
startTime,
|
|
742
|
-
endDate,
|
|
743
|
-
endTime,
|
|
744
|
-
location,
|
|
745
|
-
details
|
|
746
|
-
}) => {
|
|
747
|
-
const startDateTime = _momentTimezone.default.tz(`${startDate} ${startTime}`, "YYYY/MM/DD HH:mm", "Asia/Taipei").format("YYYYMMDDTHHmmss");
|
|
748
|
-
const endDateTime = _momentTimezone.default.tz(`${endDate} ${endTime}`, "YYYY/MM/DD HH:mm", "Asia/Taipei").format("YYYYMMDDTHHmmss");
|
|
749
|
-
const stamp = (0, _momentTimezone.default)().utc().format("YYYYMMDDTHHmmss") + "Z";
|
|
750
|
-
const uid = Date.now().toString(36) + Math.random().toString(36).substring(2, 5) + "@gemini-app.com";
|
|
751
|
-
const escapeIcs = text => text.replace(/\\/g, "\\\\").replace(/,/g, "\\,").replace(/;/g, "\\;").replace(/\n/g, "\\n");
|
|
752
|
-
const lines = ["BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//Gemini AI//NONSGML v1.0//EN", "BEGIN:VEVENT", `UID:${uid}`, `DTSTAMP:${stamp}`];
|
|
753
|
-
if (startDateTime) lines.push(`DTSTART;TZID=Asia/Taipei:${startDateTime}`);
|
|
754
|
-
if (endDateTime) lines.push(`DTEND;TZID=Asia/Taipei:${endDateTime}`);
|
|
755
|
-
if (title) lines.push(`SUMMARY:${escapeIcs(title)}`);
|
|
756
|
-
if (location) lines.push(`LOCATION:${escapeIcs(location)}`);
|
|
757
|
-
if (details) lines.push(`DESCRIPTION:${escapeIcs(details)}`);
|
|
758
|
-
lines.push("END:VEVENT", "END:VCALENDAR");
|
|
759
|
-
return lines.join("\r\n");
|
|
760
|
-
});
|
|
761
|
-
/**
|
|
762
|
-
* 根據行程物件,生成所有主要的行事曆新增連結 (Google, TimeTree, iCalendar/ICS)。
|
|
763
|
-
* @param {object} event - 行程物件。
|
|
764
|
-
* @param {string} event.title - 行程主題。
|
|
765
|
-
* @param {string} event.startDate - 開始日期 (YYYY/MM/DD)。
|
|
766
|
-
* @param {string} event.startTime - 開始時間 (HH:MM)。
|
|
767
|
-
* @param {string} event.endDate - 結束日期 (YYYY/MM/DD)。
|
|
768
|
-
* @param {string} event.endTime - 結束時間 (HH:MM)。
|
|
769
|
-
* @param {string} event.location - 地點/地址。
|
|
770
|
-
* @param {string} event.details - 描述/備註。
|
|
771
|
-
* @returns {{google: string, timeTree: string, ics: string}} 包含所有連結的物件。
|
|
772
|
-
*/
|
|
773
|
-
(0, _defineProperty2.default)(this, "generateAllCalendarLinks", event => {
|
|
774
|
-
// 1. Google Calendar Link
|
|
775
|
-
const googleLink = this.generateGoogleCalendarLink(event);
|
|
776
|
-
|
|
777
|
-
// 2. TimeTree Link
|
|
778
|
-
// TimeTree 使用 'memo' 參數,因此將 event.details 映射到 memo
|
|
779
|
-
const timeTreeEvent = {
|
|
780
|
-
...event,
|
|
781
|
-
memo: event.details
|
|
782
|
-
};
|
|
783
|
-
const timeTreeLink = this.generateTimeTreeLink(timeTreeEvent);
|
|
784
|
-
|
|
785
|
-
// 3. iCalendar (ICS) Link (Data URI 格式,用於 iOS/Outlook/下載)
|
|
786
|
-
const icsContent = this.generateIcsContent(event);
|
|
787
|
-
const icsLink = `data:text/calendar;charset=utf8,${encodeURIComponent(icsContent)}`;
|
|
788
|
-
return {
|
|
789
|
-
google: googleLink,
|
|
790
|
-
timeTree: timeTreeLink,
|
|
791
|
-
ics: icsLink // iCalendar 連結
|
|
792
|
-
};
|
|
793
|
-
});
|
|
794
|
-
/**
|
|
795
|
-
* @param {Array<Object>} current - 要更新的目標陣列
|
|
796
|
-
* @param {Array<Object>} reference - 用來替換屬性的參照陣列
|
|
797
|
-
* @returns {Array<Object>} - 更新後的陣列
|
|
798
|
-
*
|
|
799
|
-
* const current = [{ value: 1, label: 'one' }, { value: 2, label: 'two' }];
|
|
800
|
-
* const reference = [{ value: 1, label: 'ㄧ', type: 'a' }, { value: 2, label: '二', type: 'b' }, { value: 3, label: '三' }, { value: 3, label: '四' }];
|
|
801
|
-
* const latest = updateArrayByReference(current, reference);
|
|
802
|
-
* console.log(latest);
|
|
803
|
-
* 預期輸出:
|
|
804
|
-
* [
|
|
805
|
-
* { value: 1, label: 'ㄧ', type: 'a' },
|
|
806
|
-
* { value: 2, label: '二', type: 'b' }
|
|
807
|
-
* ]
|
|
808
|
-
*/
|
|
809
|
-
(0, _defineProperty2.default)(this, "getArrayOfMappingRef", (current, reference) => {
|
|
810
|
-
// 使用 _.map 迭代 current 陣列,為每個物件建立一個新物件
|
|
811
|
-
return _lodash.default.map(current, currentObj => {
|
|
812
|
-
// 使用 _.find 在 reference 陣列中尋找與 currentObj.value 相同的物件
|
|
813
|
-
const referenceObj = _lodash.default.find(reference, {
|
|
814
|
-
value: currentObj.value
|
|
815
|
-
});
|
|
816
|
-
// 如果找到對應的參考物件,則使用 _.merge 來合併它們
|
|
817
|
-
// _.merge 會將 referenceObj 的屬性覆蓋到 currentObj 上
|
|
818
|
-
// 否則,返回原始的 currentObj
|
|
819
|
-
return referenceObj ? _lodash.default.merge({}, currentObj, referenceObj) : currentObj;
|
|
820
|
-
});
|
|
821
|
-
});
|
|
822
|
-
/**
|
|
823
|
-
* 檢查多個鍵 (authorId 和 teamId)
|
|
824
|
-
* const result2 = areAllValuesTheSameOnKeys(items, 'authorId', 'teamId');
|
|
825
|
-
* console.log(`檢查 authorId 和 teamId: ${result2}`); // 輸出: true (A, T1 都相同)
|
|
826
|
-
*/
|
|
827
|
-
(0, _defineProperty2.default)(this, "areAllValuesTheSameOnKeys", (array, ...keys) => {
|
|
828
|
-
// 處理邊界情況:陣列為空、只有一個元素,或沒有指定任何鍵時,直接返回 true
|
|
829
|
-
if (!array || array.length <= 1 || keys.length === 0) {
|
|
830
|
-
return true;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// 依序檢查每個傳入的 keyName
|
|
834
|
-
for (const keyName of keys) {
|
|
835
|
-
// 確保第一個元素有所需的鍵,避免不必要的檢查
|
|
836
|
-
// 使用 ES10 可選鏈 (Optional Chaining)
|
|
837
|
-
const firstValue = array[0]?.[keyName];
|
|
838
|
-
|
|
839
|
-
// 使用 _.every 檢查陣列中所有元素,確保其值與第一個元素的值相同
|
|
840
|
-
const isCurrentKeySame = _lodash.default.every(array, item => {
|
|
841
|
-
return item[keyName] === firstValue;
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
// 只要發現有一個鍵的值不相同,立即返回 false
|
|
845
|
-
if (!isCurrentKeySame) {
|
|
846
|
-
return false;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// 如果所有鍵都通過了檢查,則返回 true
|
|
851
|
-
return true;
|
|
852
|
-
});
|
|
853
|
-
this.init();
|
|
854
|
-
this.env = "dev";
|
|
855
|
-
}
|
|
856
|
-
performActionWithoutTimingIssue(task = () => true, wait = 10) {
|
|
857
|
-
this.syncDelay(wait).then(() => task());
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
/**
|
|
861
|
-
* 執行為了避免沒意義的任務重複執行, 像是search 輸入關鍵字後, 不應該每次onchange就呼叫一次建議列表, 應該等到打完後500ms後在去 執行搜尋任務
|
|
862
|
-
* */
|
|
863
|
-
executeTimeoutTask(functionOfAsyncTask, ms = 1000, id = this.getRandomHash(), ...params) {
|
|
864
|
-
const self = this;
|
|
865
|
-
const idOfCurrentTimeoutTask = this.mapOfIdNTimeoutId[id];
|
|
866
|
-
if (idOfCurrentTimeoutTask) clearTimeout(idOfCurrentTimeoutTask);
|
|
867
|
-
const idOfTimeoutTask = setTimeout(async (...param) => {
|
|
868
|
-
await functionOfAsyncTask();
|
|
869
|
-
delete self.mapOfIdNTimeoutId[id];
|
|
870
|
-
}, ms, ...params);
|
|
871
|
-
self.mapOfIdNTimeoutId[id] = idOfTimeoutTask;
|
|
872
|
-
}
|
|
873
|
-
printLogMessage(message, error = false, ...infos) {
|
|
874
|
-
if (!this.isProductionEnvironment()) {
|
|
875
|
-
if (error) {
|
|
876
|
-
this.appendError(message, ...infos);
|
|
877
|
-
} else {
|
|
878
|
-
this.appendInfo(message, ...infos);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
init() {
|
|
883
|
-
// this.enrichZhTw();
|
|
884
|
-
}
|
|
885
|
-
setEnvironment(env) {
|
|
886
|
-
this.env = env;
|
|
887
|
-
}
|
|
888
|
-
appendInfo(...logs) {
|
|
889
|
-
if (this.isProductionEnvironment()) return;
|
|
890
|
-
console.log(...logs);
|
|
891
|
-
}
|
|
892
|
-
appendError(...logs) {
|
|
893
|
-
if (this.isProductionEnvironment()) return;
|
|
894
|
-
console.error(...logs);
|
|
895
|
-
}
|
|
896
|
-
async syncDelay(delayInms = 2000) {
|
|
897
|
-
return new Promise(resolve => {
|
|
898
|
-
setTimeout(() => {
|
|
899
|
-
resolve(delayInms);
|
|
900
|
-
}, delayInms);
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
/**
|
|
905
|
-
* sample:
|
|
906
|
-
*
|
|
907
|
-
* string = `@desktop: ~"only screen and (min-width: 600px) and (max-width: 1680px)";`;
|
|
908
|
-
* rule = '@[desktop|mobile|desktop]';
|
|
909
|
-
* return true | false
|
|
910
|
-
*
|
|
911
|
-
*/
|
|
912
|
-
startWithRegex(string = "", rule = ".") {
|
|
913
|
-
let pattern = new RegExp(`^${rule}`, "i");
|
|
914
|
-
return pattern.test(string);
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* 把敘述句和條件子句累加在一起,讓其變成一個合理的functional呼叫
|
|
918
|
-
* condition2(condition1(target)) => 為了應付 collection(path).where('age','>',11).orderBy('age');
|
|
919
|
-
* */
|
|
920
|
-
accumulate(target, conditions) {
|
|
921
|
-
let beginning = target;
|
|
922
|
-
for (const condition of conditions) {
|
|
923
|
-
if (condition !== undefined && _lodash.default.isFunction(condition)) {
|
|
924
|
-
beginning = condition(beginning);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
return beginning;
|
|
928
|
-
}
|
|
929
|
-
isOrEquals(target, ...several) {
|
|
930
|
-
for (const each of several) {
|
|
931
|
-
if (_lodash.default.isEqual(target, each)) return true;
|
|
932
|
-
}
|
|
933
|
-
return false;
|
|
934
|
-
}
|
|
935
|
-
isAndEquals(...predicates) {
|
|
936
|
-
for (const predicate of predicates) {
|
|
937
|
-
if (!predicate()) {
|
|
938
|
-
return false;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
return true;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/** 取得reg match 第一個項目, 不然好煩呀 */
|
|
945
|
-
getStringOfHeadMatch(string, regex, flag = "g") {
|
|
946
|
-
const result = string.match(new RegExp(regex, flag));
|
|
947
|
-
return this.isUndefinedNullEmpty(result) ? undefined : result[0];
|
|
948
|
-
}
|
|
949
|
-
or(...booleans) {
|
|
950
|
-
for (const boo of booleans) {
|
|
951
|
-
if (_lodash.default.isBoolean(boo) && boo) return true;
|
|
952
|
-
}
|
|
953
|
-
return false;
|
|
954
|
-
}
|
|
955
|
-
and(...booleans) {
|
|
956
|
-
for (const boo of booleans) {
|
|
957
|
-
if (!!!boo) return false;
|
|
958
|
-
}
|
|
959
|
-
return true;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
/**
|
|
963
|
-
*
|
|
964
|
-
* const array = [1,2,3,4,5,6,7,8];
|
|
965
|
-
* nth(array, -9)
|
|
966
|
-
* // => 8
|
|
967
|
-
* */
|
|
968
|
-
nth(array, index = -1) {
|
|
969
|
-
return _lodash.default.nth(array, index % _lodash.default.size(array));
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
/** 選一個exist的candidate回傳, 像是firebase 可以 idToken 又可以 oauthIdToken*/
|
|
973
|
-
getExistOne(...candidates) {
|
|
974
|
-
for (const candidate of candidates) {
|
|
975
|
-
if (candidate) return candidate;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
/** '###string' => 'string' */
|
|
980
|
-
getStringOfDropHeadSign(string, sign) {
|
|
981
|
-
return _lodash.default.dropWhile(Array.from(string), each => _lodash.default.isEqual(each, sign)).join("");
|
|
982
|
-
}
|
|
983
|
-
isAndWith(self, predicate, ...several) {
|
|
984
|
-
for (const each of several) {
|
|
985
|
-
if (!predicate(self, each)) return false;
|
|
986
|
-
}
|
|
987
|
-
return true;
|
|
988
|
-
}
|
|
989
|
-
async syncDelayRandom(min = 3000, max = 5000) {
|
|
990
|
-
const random = this.getRandomValue(min, max);
|
|
991
|
-
await this.syncDelay(random);
|
|
992
|
-
return random;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
/** 如果是array,用 indexOf檢查each
|
|
996
|
-
* 如果是object,看有沒有這個key
|
|
997
|
-
* 如果是string, 就檢查有沒有包含
|
|
998
|
-
* precisely 就是用findIndex,去比較value
|
|
999
|
-
*
|
|
1000
|
-
* */
|
|
1001
|
-
has(collection, item, precisely = false) {
|
|
1002
|
-
if (_lodash.default.isArray(collection)) {
|
|
1003
|
-
if (precisely) return _lodash.default.findIndex(collection, each => _lodash.default.isEqual(item, each)) > -1;else return _lodash.default.indexOf(collection, item) > -1;
|
|
1004
|
-
}
|
|
1005
|
-
if (_lodash.default.isObject(item)) {
|
|
1006
|
-
return collection[item];
|
|
1007
|
-
}
|
|
1008
|
-
if (_lodash.default.isString(collection)) {
|
|
1009
|
-
return collection.indexOf(item) > -1;
|
|
1010
|
-
}
|
|
1011
|
-
return false;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
/** 就是比較_.isEqual(isEqual的註解很重要), 不是用address去判斷 */
|
|
1015
|
-
containsBy(array, item) {
|
|
1016
|
-
return _lodash.default.findIndex(array, each => _lodash.default.isEqual(each, item)) >= 0;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
/** (Parentheses) */
|
|
1020
|
-
getStringOfInsideParentheses(string, rule = `.`) {
|
|
1021
|
-
return this.getStringOfRule(string, rule, "(", ")");
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
/** [Brackets] */
|
|
1025
|
-
getStringOfInsideBrackets(string, rule = `.`) {
|
|
1026
|
-
return this.getStringOfRule(string, rule, "[", "]");
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
/** {Braces} */
|
|
1030
|
-
getStringOfInsideBraces(string, rule = `.`) {
|
|
1031
|
-
return this.getStringOfRule(string, rule, "{", "}");
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
/** rules 只抓文字 [\\w] |*/
|
|
1035
|
-
getStringOfRule(string, rule = `.`, left = "{", right = "}") {
|
|
1036
|
-
return this.getStringOfHeadMatch(string, `(?<=\\${left})${rule}+?(?=\\${right})`);
|
|
1037
|
-
}
|
|
1038
|
-
getRandomHash(length = 20) {
|
|
1039
|
-
const randomBytes = _cryptoJs.default.lib.WordArray.random(length);
|
|
1040
|
-
const base64String = _cryptoJs.default.enc.Base64.stringify(randomBytes);
|
|
1041
|
-
// 根據需要調整格式
|
|
1042
|
-
return base64String.substring(0, length);
|
|
1043
|
-
}
|
|
1044
|
-
getRandomHashV2(length) {
|
|
1045
|
-
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
1046
|
-
let result = "";
|
|
1047
|
-
for (let i = 0; i < length; i++) {
|
|
1048
|
-
const randomIndex = Math.floor(Math.random() * characters.length);
|
|
1049
|
-
result += characters.charAt(randomIndex);
|
|
1050
|
-
}
|
|
1051
|
-
return result;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
/** alwaysTheSame 就是產出的encrypt value會固定(適合用在欄位的key), 不然會產生隨機偏移量, 但皆不影響解譯 */
|
|
1055
|
-
getEncryptString(texts, key = _configerer.configerer.ENCRYPT_KEY, alwaysTheSame = false) {
|
|
1056
|
-
const maxLengthOfKey = 22;
|
|
1057
|
-
if (key.length > maxLengthOfKey) throw new _exceptioner.default(8010, _lodash.default.size(key));
|
|
1058
|
-
/** 帶入偏移量, keyOfkeyOfCrypto 需要是長度為22的字串, 太獵奇了*/
|
|
1059
|
-
const ivOfCrypto = _cryptoJs.default.enc.Base64.parse("thisIsIVWeNeedToGenerateTheSameValue");
|
|
1060
|
-
const keyOfCrypto = alwaysTheSame ? _cryptoJs.default.enc.Base64.parse(`${key}${_lodash.default.range(0, maxLengthOfKey - key.length).join("")}`) : key;
|
|
1061
|
-
return _cryptoJs.default.AES.encrypt(texts, keyOfCrypto, {
|
|
1062
|
-
iv: ivOfCrypto
|
|
1063
|
-
}).toString();
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
/** alwaysTheSame 就是產出的encrypt value會固定(適合用在欄位的key), 不然會產生隨機偏移量, 但皆不影響解譯 */
|
|
1067
|
-
getEncryptStringV2(texts, key = _configerer.configerer.ENCRYPT_KEY, alwaysTheSame = false) {
|
|
1068
|
-
const maxLengthOfKey = 22;
|
|
1069
|
-
if (key.length > maxLengthOfKey) throw new _exceptioner.default(8010, _lodash.default.size(key));
|
|
1070
|
-
/** 帶入偏移量, keyOfkeyOfCrypto 需要是長度為22的字串, 太獵奇了*/
|
|
1071
|
-
const ivOfCrypto = _cryptoJs.default.enc.Base64.parse("thisIsIVWeNeedToGenerateTheSameValue");
|
|
1072
|
-
const keyOfCrypto = alwaysTheSame ? _cryptoJs.default.enc.Base64.parse(`${key}${_lodash.default.range(0, maxLengthOfKey - key.length).join("")}`) : key;
|
|
1073
|
-
return _cryptoJs.default.AES.encrypt(JSON.stringify({
|
|
1074
|
-
content: texts
|
|
1075
|
-
}), keyOfCrypto, {
|
|
1076
|
-
iv: ivOfCrypto
|
|
1077
|
-
}).toString();
|
|
1078
|
-
}
|
|
1079
|
-
getDecryptString(ciphertext, key = _configerer.configerer.ENCRYPT_KEY) {
|
|
1080
|
-
const maxLengthOfKey = 22;
|
|
1081
|
-
if (key.length > maxLengthOfKey) throw new _exceptioner.default(8010, _lodash.default.size(key));
|
|
1082
|
-
const ivOfCrypto = _cryptoJs.default.enc.Base64.parse("thisIsIVWeNeedToGenerateTheSameValue");
|
|
1083
|
-
try {
|
|
1084
|
-
const value = _cryptoJs.default.AES.decrypt(ciphertext, key, {
|
|
1085
|
-
iv: ivOfCrypto
|
|
1086
|
-
}).toString(_cryptoJs.default.enc.Utf8);
|
|
1087
|
-
if (!_lodash.default.isEmpty(value.trim())) return value;
|
|
1088
|
-
} catch (e) {
|
|
1089
|
-
/** 把問題給吃掉了, 也不能紀錄, 因為用了appendError*/
|
|
1090
|
-
}
|
|
1091
|
-
return _cryptoJs.default.AES.decrypt(ciphertext, _cryptoJs.default.enc.Base64.parse(`${key}${_lodash.default.range(0, maxLengthOfKey - key.length).join("")}`), {
|
|
1092
|
-
iv: ivOfCrypto
|
|
1093
|
-
}).toString(_cryptoJs.default.enc.Utf8);
|
|
1094
|
-
}
|
|
1095
|
-
getDecryptStringV2(ciphertext, key = _configerer.configerer.ENCRYPT_KEY) {
|
|
1096
|
-
const maxLengthOfKey = 22;
|
|
1097
|
-
if (key.length > maxLengthOfKey) throw new _exceptioner.default(8010, _lodash.default.size(key));
|
|
1098
|
-
const ivOfCrypto = _cryptoJs.default.enc.Base64.parse("thisIsIVWeNeedToGenerateTheSameValue");
|
|
1099
|
-
try {
|
|
1100
|
-
const stringOfObj = _cryptoJs.default.AES.decrypt(ciphertext, key, {
|
|
1101
|
-
iv: ivOfCrypto
|
|
1102
|
-
}).toString(_cryptoJs.default.enc.Utf8);
|
|
1103
|
-
if (!_lodash.default.isEmpty(stringOfObj.trim())) {
|
|
1104
|
-
const obj = JSON.parse(stringOfObj);
|
|
1105
|
-
return obj.content;
|
|
1106
|
-
}
|
|
1107
|
-
} catch (e) {
|
|
1108
|
-
/** 把問題給吃掉了, 也不能紀錄, 因為用了appendError*/
|
|
1109
|
-
}
|
|
1110
|
-
const stringOfObj = _cryptoJs.default.AES.decrypt(ciphertext, _cryptoJs.default.enc.Base64.parse(`${key}${_lodash.default.range(0, maxLengthOfKey - key.length).join("")}`), {
|
|
1111
|
-
iv: ivOfCrypto
|
|
1112
|
-
}).toString(_cryptoJs.default.enc.Utf8);
|
|
1113
|
-
const obj = JSON.parse(stringOfObj);
|
|
1114
|
-
return obj.content;
|
|
1115
|
-
}
|
|
1116
|
-
getFirebaseFormattedString(texts) {
|
|
1117
|
-
return _lodash.default.replace(texts, /[\.\#\$\[\]]/g, "-").trim();
|
|
1118
|
-
}
|
|
1119
|
-
formalizeNamesToArray(singerString) {
|
|
1120
|
-
let normalize = singerString;
|
|
1121
|
-
/** avoid this situation, 演唱:陳勢安、畢書盡 (Bii) 編曲:Jerry C */
|
|
1122
|
-
normalize = normalize.split(_configerer.configerer.SEPARATE_TONE_SINGER)[0].trim();
|
|
1123
|
-
normalize = _lodash.default.replace(normalize, /[,\/#!$%\^&\*;:{}=_`、~()()]/g, "_").trim();
|
|
1124
|
-
/** avoid this situation, 陳勢安_畢書盡__Bii_ */
|
|
1125
|
-
normalize = this.getFirebaseFormattedString(normalize);
|
|
1126
|
-
normalize = _lodash.default.replace(normalize, /\_\_+/g, "_").trim();
|
|
1127
|
-
while (_lodash.default.endsWith(normalize, "_")) {
|
|
1128
|
-
/** avoid this situation, 陳勢安_畢書盡_Bii_ */
|
|
1129
|
-
normalize = normalize.slice(0, -1).trim();
|
|
1130
|
-
}
|
|
1131
|
-
const words = normalize.split("_");
|
|
1132
|
-
/** avoid this situation, ["畢書盡 ","Bii","陳勢安 "] */
|
|
1133
|
-
return _lodash.default.map(words, word => _lodash.default.trim(word));
|
|
1134
|
-
}
|
|
1135
|
-
getShuffledArrayWithLimitCountHighPerformance(arr, n) {
|
|
1136
|
-
let result = new Array(n),
|
|
1137
|
-
len = arr.length,
|
|
1138
|
-
taken = new Array(len);
|
|
1139
|
-
if (n > len) n = len; // Handle n > arr.length case gracefully
|
|
1140
|
-
|
|
1141
|
-
while (n--) {
|
|
1142
|
-
let x = Math.floor(Math.random() * len);
|
|
1143
|
-
result[n] = arr[x in taken ? taken[x] : x];
|
|
1144
|
-
taken[x] = --len in taken ? taken[len] : len;
|
|
1145
|
-
}
|
|
1146
|
-
return result;
|
|
1147
|
-
}
|
|
1148
|
-
getFileNameFromPath(path, extension = false) {
|
|
1149
|
-
const segments = path.split("/");
|
|
1150
|
-
const target = segments.pop();
|
|
1151
|
-
return extension ? target : target.split(".").shift();
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/** http://wnj.cdji/david.mp3 => david.mp3 */
|
|
1155
|
-
getFileNameExtensionFromPath(path) {
|
|
1156
|
-
const name = path.split("/").pop();
|
|
1157
|
-
return name;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
/** getPathOfReplaceLastDir('folder1/folder2/folder3', 'folder7') => 'folder1/folder2/folder7' */
|
|
1161
|
-
getPathOfReplaceLastDir(path, name) {
|
|
1162
|
-
const split = path.split("/");
|
|
1163
|
-
split.pop();
|
|
1164
|
-
split.push(name);
|
|
1165
|
-
return split.join("/");
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
/** http://wnj.cdji/david.mp3 => mp3 */
|
|
1169
|
-
getExtensionFromPath(path) {
|
|
1170
|
-
const name = path.split("/").pop();
|
|
1171
|
-
const segments = name.split(".");
|
|
1172
|
-
return _lodash.default.size(segments) > 1 ? segments.pop() : "";
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
/** ../folderName/fileName.xxx => ./folderName */
|
|
1176
|
-
getFolderPathOfSpecificPath(path) {
|
|
1177
|
-
const split = path.split("/");
|
|
1178
|
-
split.pop();
|
|
1179
|
-
return split.join("/");
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
/**
|
|
1183
|
-
* 取得folderName
|
|
1184
|
-
* console.log(utiller.getFolderNameOfFilePath(`das/asdiasjiosd/jif/d.js`)); //ans:'jif'
|
|
1185
|
-
* */
|
|
1186
|
-
getFolderNameOfFilePath(path) {
|
|
1187
|
-
if (this.isValidFilePath(path)) {
|
|
1188
|
-
const splits = path.split("/");
|
|
1189
|
-
return _lodash.default.nth(splits, -2);
|
|
1190
|
-
} else {
|
|
1191
|
-
throw new _exceptioner.default(9999, `64255615 path is not valid '${path}'`);
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
/** absolute=> /acc/bbv/{target}/index.js 檢查有沒有在他下面 */
|
|
1196
|
-
isUnderTargetPath(absolute, target) {
|
|
1197
|
-
const segments = absolute.split("/");
|
|
1198
|
-
return this.has(segments, target);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
/** 取得檔案的目錄, path => c://folderName/fileName.js to c://folderName */
|
|
1202
|
-
getFileDirPath(path, slash = true) {
|
|
1203
|
-
return _lodash.default.join(_lodash.default.initial(_lodash.default.split(path, "/")), "/") + (slash ? "/" : "");
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
/** path ==> /asd/cc/dfj/jei3.mp3 => */
|
|
1207
|
-
isPathEqualsFileType(path, type) {
|
|
1208
|
-
const extension = path.split(".").pop();
|
|
1209
|
-
return _lodash.default.isEqual(extension, type);
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
/** 是一個/a/b/c.js 的檔案路徑 */
|
|
1213
|
-
isValidFilePath(path) {
|
|
1214
|
-
const extension = this.getExtensionFromPath(path);
|
|
1215
|
-
return _lodash.default.size(extension) > 0;
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/** 拿前面n個items */
|
|
1219
|
-
getArrayOfSize(array, n = 1) {
|
|
1220
|
-
return _lodash.default.take(array, n);
|
|
1221
|
-
}
|
|
1222
|
-
getShuffledArrayWithLimitCount(arr, n) {
|
|
1223
|
-
return this.getShuffledArrayWithLimitCountHighPerformance(arr, n); // 使用已優化的版本
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/** ignore 就是黑名單的意思 */
|
|
1227
|
-
getRandomItemOfArray(array, ...ignores) {
|
|
1228
|
-
if (!_lodash.default.isArray(array)) throw new _exceptioner.default(9999, `why are you so stupid, typeof array should be array, not ==> ${array} `);
|
|
1229
|
-
const filter = _lodash.default.without(array, ...ignores);
|
|
1230
|
-
const target = _lodash.default.size(filter) > 0 ? filter : array;
|
|
1231
|
-
const item = this.getShuffledArrayWithLimitCount(target, 1);
|
|
1232
|
-
return item.length > 0 ? item[0] : undefined;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
/**
|
|
1236
|
-
* const aaa = {};
|
|
1237
|
-
* utiller.appendMapOfKeyArray(aaa, 'a', 11);
|
|
1238
|
-
* utiller.appendMapOfKeyArray(aaa, 'c', 13);
|
|
1239
|
-
* utiller.appendMapOfKeyArray(aaa, 'a', 23);
|
|
1240
|
-
* utiller.appendMapOfKeyArray(aaa, 'c', 'vsdd')
|
|
1241
|
-
* utiller.appendMapOfKeyArray(aaa, 'a', 'sd');
|
|
1242
|
-
* console.log(aaa);
|
|
1243
|
-
* // { a: [ 11, 23, 'sd' ], c: [ 13, 'vsdd' ] }
|
|
1244
|
-
* */
|
|
1245
|
-
appendMapOfKeyArray(object, key, ...value) {
|
|
1246
|
-
if (this.isUndefinedNullEmpty(object[key])) {
|
|
1247
|
-
object[key] = [...value];
|
|
1248
|
-
} else {
|
|
1249
|
-
object[key].push(...value);
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
/**
|
|
1254
|
-
* 優化版本:針對基於唯一 Key 的合併
|
|
1255
|
-
* @param {Array} major - 主要陣列
|
|
1256
|
-
* @param {Array} sub - 次要陣列
|
|
1257
|
-
* @param {string} key - 用於匹配的唯一鍵名 (e.g., 'id')
|
|
1258
|
-
* @returns {Array} - 合併後的新陣列
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1261
|
-
* util.getMergedArrayBy(
|
|
1262
|
-
[{id: 123, name: 'david'}, {id: 321, name: 'Joe'}],
|
|
1263
|
-
[{id: 321, age: 13}, {id: 123, age: 30}],
|
|
1264
|
-
'id')
|
|
1265
|
-
*
|
|
1266
|
-
* result:
|
|
1267
|
-
[
|
|
1268
|
-
{ id: 123, age: 30, name: 'david' },
|
|
1269
|
-
{ id: 321, age: 13, name: 'Joe' }
|
|
1270
|
-
]
|
|
1271
|
-
*
|
|
1272
|
-
*/
|
|
1273
|
-
getMergedArrayBy(major = [], sub = [], key) {
|
|
1274
|
-
if (!key || major.length === 0 || sub.length === 0) {
|
|
1275
|
-
// 如果沒有 key 或任一陣列為空,無法優化或無需合併,回傳 major 的淺拷貝
|
|
1276
|
-
return [...major];
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// 1. 將 sub 陣列轉換為以 key 為鍵的 Map,時間複雜度 O(N)
|
|
1280
|
-
const subMap = new Map(sub.map(item => [item[key], item]));
|
|
1281
|
-
|
|
1282
|
-
// 2. 遍歷 major 陣列,從 Map 中查找匹配項,時間複雜度 O(M)
|
|
1283
|
-
return major.map(majorItem => {
|
|
1284
|
-
const subItem = subMap.get(majorItem[key]);
|
|
1285
|
-
// 合併找到的 subItem 和 majorItem,majorItem 的屬性優先
|
|
1286
|
-
return {
|
|
1287
|
-
...(subItem || {}),
|
|
1288
|
-
...majorItem
|
|
1289
|
-
};
|
|
1290
|
-
});
|
|
1291
|
-
// 整體時間複雜度約為 O(M + N)
|
|
1292
|
-
}
|
|
1293
|
-
getShuffledItemFromArray(arr) {
|
|
1294
|
-
let shuffled = _lodash.default.shuffle(arr);
|
|
1295
|
-
return shuffled[0];
|
|
1296
|
-
}
|
|
1297
|
-
getShuffledArray(arr) {
|
|
1298
|
-
let shuffled = _lodash.default.shuffle(arr);
|
|
1299
|
-
return shuffled;
|
|
1300
|
-
}
|
|
1301
|
-
isJson(item) {
|
|
1302
|
-
item = typeof item !== "string" ? JSON.stringify(item) : item;
|
|
1303
|
-
try {
|
|
1304
|
-
item = JSON.parse(item);
|
|
1305
|
-
} catch (e) {
|
|
1306
|
-
return false;
|
|
1307
|
-
}
|
|
1308
|
-
if (typeof item === "object" && item !== null) {
|
|
1309
|
-
return true;
|
|
1310
|
-
}
|
|
1311
|
-
return false;
|
|
1312
|
-
}
|
|
1313
|
-
getObjectValue(obj) {
|
|
1314
|
-
if (_lodash.default.isObject(obj)) {
|
|
1315
|
-
return Object.values(obj)[0];
|
|
1316
|
-
}
|
|
1317
|
-
return "";
|
|
1318
|
-
}
|
|
1319
|
-
getObject(key, value) {
|
|
1320
|
-
const object = {};
|
|
1321
|
-
object[key] = value;
|
|
1322
|
-
return object;
|
|
1323
|
-
}
|
|
1324
|
-
getStringOfCreditCardFormatted(string = 0) {
|
|
1325
|
-
const inputValue = string.replace(/\D/g, ""); // Remove all non-digit characters
|
|
1326
|
-
const result = inputValue.replace(/(\d{4})(?=\d)/g, "$1-"); // Add a dash every 4 digits
|
|
1327
|
-
return result.slice(0, 19);
|
|
1328
|
-
}
|
|
1329
|
-
getObjectKey(obj) {
|
|
1330
|
-
if (_lodash.default.isObject(obj)) {
|
|
1331
|
-
return Object.keys(obj)[0];
|
|
1332
|
-
}
|
|
1333
|
-
return "";
|
|
1334
|
-
}
|
|
1335
|
-
printf() {
|
|
1336
|
-
this.appendInfo("i can use in web || react.js");
|
|
1337
|
-
}
|
|
1338
|
-
isKeywordRule(constraint) {
|
|
1339
|
-
if (_lodash.default.isUndefined(constraint) || _lodash.default.isEmpty(constraint)) throw new Error("PARAMS CAN NOT BE EMPTY");
|
|
1340
|
-
if (!_lodash.default.isString(constraint)) throw new Error("PARAMS SHOULD BE STRING");
|
|
1341
|
-
if (constraint.length > 20) throw new Error("EXCEED 20 WORDS IS NOT ALLOWED");
|
|
1342
|
-
}
|
|
1343
|
-
getItsKeyByValue(object, value) {
|
|
1344
|
-
return Object.keys(object).find(key => object[key] === value);
|
|
1345
|
-
}
|
|
1346
|
-
startWiths(string, key = []) {
|
|
1347
|
-
for (const _key of key) {
|
|
1348
|
-
if (_lodash.default.startsWith(string, _key)) {
|
|
1349
|
-
return true;
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
return false;
|
|
1353
|
-
}
|
|
1354
|
-
replaceAll(string, patten, to) {
|
|
1355
|
-
return _lodash.default.replace(string, new RegExp(`${patten}`, `g`), to); /** g就是 global */
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
/** pattern => {from:'㊟',to:'注'}, {from:'\\(土\\)',to:'(土)'}*/
|
|
1359
|
-
replaceAllWithSets(string = "", ...patterns) {
|
|
1360
|
-
let after = string;
|
|
1361
|
-
for (const pattern of patterns) {
|
|
1362
|
-
if (this.isOrEquals(undefined, pattern.from, pattern.to)) {
|
|
1363
|
-
throw (0, _exceptioner.default)(9999, `from or to can't be empty`);
|
|
1364
|
-
}
|
|
1365
|
-
after = this.replaceAll(after, pattern.from, pattern.to);
|
|
1366
|
-
}
|
|
1367
|
-
return after;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
/** 就是用address去找出current index(比較內文要用findIndex),然後取代之
|
|
1371
|
-
* array = ['a','b','c'];
|
|
1372
|
-
* current = array[1] === 'b'
|
|
1373
|
-
* latest = 'd'
|
|
1374
|
-
* return ['a','d','c']
|
|
1375
|
-
* */
|
|
1376
|
-
replaceArrayByContentIndex(array, current, latest) {
|
|
1377
|
-
const index = _lodash.default.indexOf(array, current);
|
|
1378
|
-
array[index] = latest;
|
|
1379
|
-
}
|
|
1380
|
-
deepFlat(collection, sign = "_") {
|
|
1381
|
-
let result = "";
|
|
1382
|
-
const stack = [[collection, ""]]; // 儲存 [項目, 目前的前綴]
|
|
1383
|
-
|
|
1384
|
-
while (stack.length > 0) {
|
|
1385
|
-
const [current, prefix] = stack.pop();
|
|
1386
|
-
if (_lodash.default.isArray(current)) {
|
|
1387
|
-
// 將陣列元素反向推入堆疊以保持順序
|
|
1388
|
-
for (let i = current.length - 1; i >= 0; i--) {
|
|
1389
|
-
stack.push([current[i], prefix]); // 陣列元素不加前綴
|
|
1390
|
-
}
|
|
1391
|
-
} else if (_lodash.default.isObject(current)) {
|
|
1392
|
-
// 將物件鍵值對反向推入堆疊
|
|
1393
|
-
const keys = Object.keys(current);
|
|
1394
|
-
for (let i = keys.length - 1; i >= 0; i--) {
|
|
1395
|
-
const key = keys[i];
|
|
1396
|
-
// 值推入堆疊,前綴包含當前鍵
|
|
1397
|
-
stack.push([current[key], prefix + key + sign]);
|
|
1398
|
-
}
|
|
1399
|
-
} else {
|
|
1400
|
-
// 基本型別,添加到結果字串
|
|
1401
|
-
const valueString = _lodash.default.trim(String(current)); // 確保轉為字串並去除頭尾空白
|
|
1402
|
-
if (valueString.length > 0) {
|
|
1403
|
-
// 避免添加空字串或只有空白的字串
|
|
1404
|
-
result += (result.length > 0 ? sign : "") + prefix + valueString;
|
|
1405
|
-
} else if (prefix.length > 0 && result.length > 0) {
|
|
1406
|
-
// 如果值為空但有前綴,且結果已非空,則添加分隔符
|
|
1407
|
-
result += sign;
|
|
1408
|
-
} else if (prefix.length > 0 && result.length === 0) {
|
|
1409
|
-
// 如果值為空但有前綴,且結果為空,則只添加前綴(去掉末尾的 sign)
|
|
1410
|
-
result += prefix.endsWith(sign) ? prefix.slice(0, -sign.length) : prefix;
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
// 最後可能需要處理結尾多餘的 sign
|
|
1415
|
-
if (result.endsWith(sign)) {
|
|
1416
|
-
result = result.slice(0, -sign.length);
|
|
1417
|
-
}
|
|
1418
|
-
return result;
|
|
1419
|
-
}
|
|
1420
|
-
joinEscapeChar(str) {
|
|
1421
|
-
return (str + "").replace(/[\\"']/g, "\\$&").replace(/\u0000/g, "\\0");
|
|
1422
|
-
}
|
|
1423
|
-
getValueWithIntegerType(whatever) {
|
|
1424
|
-
try {
|
|
1425
|
-
const value = parseInt(whatever);
|
|
1426
|
-
return isNaN(value) ? 0 : value;
|
|
1427
|
-
} catch (error) {
|
|
1428
|
-
return 0;
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
/** 如果有優先順序的值,需要檢查是否isUndefinedEmpty,這樣程式邏輯就不用一直 if else switch */
|
|
1433
|
-
getValueOfPriority(...compares) {
|
|
1434
|
-
for (const compare of compares) {
|
|
1435
|
-
if (!this.isUndefinedNullEmpty(compare)) return compare;
|
|
1436
|
-
}
|
|
1437
|
-
return undefined;
|
|
1438
|
-
}
|
|
1439
|
-
async asyncPool(poolLimit, array, iteratorFn) {
|
|
1440
|
-
const ret = [];
|
|
1441
|
-
const executing = [];
|
|
1442
|
-
for (const item of array) {
|
|
1443
|
-
const p = Promise.resolve().then(() => {
|
|
1444
|
-
return iteratorFn(item, array);
|
|
1445
|
-
});
|
|
1446
|
-
ret.push(p);
|
|
1447
|
-
if (poolLimit <= array.length) {
|
|
1448
|
-
const e = p.then(() => {
|
|
1449
|
-
return executing.splice(executing.indexOf(e), 1);
|
|
1450
|
-
});
|
|
1451
|
-
executing.push(e);
|
|
1452
|
-
if (executing.length >= poolLimit) {
|
|
1453
|
-
await Promise.race(executing);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
return Promise.all(ret);
|
|
1458
|
-
}
|
|
1459
|
-
getAttrValueInSequence(info, ...attrs) {
|
|
1460
|
-
for (const attr of attrs) {
|
|
1461
|
-
if (!_lodash.default.isEmpty(info[attr])) {
|
|
1462
|
-
return info[attr];
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
return info;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
// 半形轉化為全形
|
|
1469
|
-
toDBC(txtstring) {
|
|
1470
|
-
var tmp = "";
|
|
1471
|
-
for (var i = 0; i < txtstring.length; i++) {
|
|
1472
|
-
if (txtstring.charCodeAt(i) === 32) {
|
|
1473
|
-
tmp = tmp + String.fromCharCode(12288);
|
|
1474
|
-
}
|
|
1475
|
-
if (txtstring.charCodeAt(i) < 127) {
|
|
1476
|
-
tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
return tmp;
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
// 全形轉換為半形
|
|
1483
|
-
toCDB(str) {
|
|
1484
|
-
var tmp = "";
|
|
1485
|
-
for (var i = 0; i < str.length; i++) {
|
|
1486
|
-
if (str.charCodeAt(i) === 12288) {
|
|
1487
|
-
tmp += String.fromCharCode(str.charCodeAt(i) - 12256);
|
|
1488
|
-
continue;
|
|
1489
|
-
}
|
|
1490
|
-
if (str.charCodeAt(i) > 65280 && str.charCodeAt(i) < 65375) {
|
|
1491
|
-
tmp += String.fromCharCode(str.charCodeAt(i) - 65248);
|
|
1492
|
-
} else {
|
|
1493
|
-
tmp += String.fromCharCode(str.charCodeAt(i));
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
return tmp;
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
/** 用_.findIndex(比較內文的方式) 去找出array裡所有符合條件的 */
|
|
1500
|
-
findIndexes(array, predicate) {
|
|
1501
|
-
const indexes = [];
|
|
1502
|
-
let hasIndex = true;
|
|
1503
|
-
let indexOfLatest = 0;
|
|
1504
|
-
while (hasIndex) {
|
|
1505
|
-
indexOfLatest = _lodash.default.findIndex(array, predicate, indexOfLatest + 1);
|
|
1506
|
-
if (indexOfLatest > -1) {
|
|
1507
|
-
indexes.push(indexOfLatest);
|
|
1508
|
-
} else {
|
|
1509
|
-
hasIndex = false;
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
return indexes;
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
/**
|
|
1516
|
-
* 得到slice array 從指定的index
|
|
1517
|
-
* console.log(utiller.getSliceArrayOfSpecificIndexes(['a','v','c','d'], 1, 2, 3));
|
|
1518
|
-
* [ 'v', 'c', 'd' ]
|
|
1519
|
-
* */
|
|
1520
|
-
getSliceArrayOfSpecificIndexes(array, ...indexes) {
|
|
1521
|
-
const items = [];
|
|
1522
|
-
const size = _lodash.default.size(array);
|
|
1523
|
-
for (const index of indexes) {
|
|
1524
|
-
if (!_lodash.default.isNumber(index)) throw new _exceptioner.default(9999, `59941278 index should be number => ${index}, ${array}`);
|
|
1525
|
-
if (index > size - 1) throw new _exceptioner.default(9999, `5994123 index=>${index} is not valid, exceed than array size=${size}, ${array}`);
|
|
1526
|
-
items.push(_lodash.default.nth(array, index));
|
|
1527
|
-
}
|
|
1528
|
-
return items;
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
/** 找到關鍵字所有的index */
|
|
1532
|
-
indexesOf(arr, val) {
|
|
1533
|
-
const indexes = [];
|
|
1534
|
-
let i = -1;
|
|
1535
|
-
while ((i = arr.indexOf(val, i + 1)) !== -1) {
|
|
1536
|
-
indexes.push(i);
|
|
1537
|
-
}
|
|
1538
|
-
return indexes;
|
|
1539
|
-
}
|
|
1540
|
-
/** 比較內文, 不是只比較 memory address */
|
|
1541
|
-
getIndexOfContext(context, stmt) {
|
|
1542
|
-
return _lodash.default.findIndex(context, per => {
|
|
1543
|
-
return _lodash.default.isEqual(per.trim(), stmt);
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
/** 去掉文字裡討厭的換行*/
|
|
1548
|
-
toOneLineString(string) {
|
|
1549
|
-
return _lodash.default.join(_lodash.default.split(string, "\n"), "");
|
|
1550
|
-
}
|
|
1551
|
-
toSpaceLessString(string) {
|
|
1552
|
-
/** 這樣寫也可以 string.split('').map((each) => each.trim()).join(''); */
|
|
1553
|
-
return _lodash.default.split(string, "").map(each => _lodash.default.trim(each)).join("");
|
|
1554
|
-
}
|
|
1555
|
-
toNewLineLessString(string) {
|
|
1556
|
-
/** 這樣寫也可以 string.split('').map((each) => each.trim()).join(''); */
|
|
1557
|
-
return _lodash.default.split(string, "\n").map(each => _lodash.default.trim(each)).join("");
|
|
1558
|
-
}
|
|
1559
|
-
exist(obj) {
|
|
1560
|
-
return !_lodash.default.isNull(obj) && !_lodash.default.isUndefined(obj);
|
|
1561
|
-
}
|
|
1562
|
-
isUndefinedNullEmpty(obj) {
|
|
1563
|
-
const first = obj === undefined || obj === null;
|
|
1564
|
-
const second = _lodash.default.isString(obj) || _lodash.default.isArray(obj) || _lodash.default.isObject(obj) ? _lodash.default.isEmpty(obj) : false;
|
|
1565
|
-
return first || second;
|
|
1566
|
-
}
|
|
1567
|
-
isAndConditionOfUndefinedNullEmpty(...objs) {
|
|
1568
|
-
for (const obj of objs) {
|
|
1569
|
-
if (!this.isUndefinedNullEmpty(obj)) return false;
|
|
1570
|
-
}
|
|
1571
|
-
return true;
|
|
1572
|
-
}
|
|
1573
|
-
isOrConditionOfUndefinedNullEmpty(...objs) {
|
|
1574
|
-
for (const obj of objs) {
|
|
1575
|
-
if (this.isUndefinedNullEmpty(obj)) return true;
|
|
1576
|
-
}
|
|
1577
|
-
return false;
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
/** this method mutates segments */
|
|
1581
|
-
getStringHandledByEachLine(string, predict = (segment, index, segments) => true, separator = "\n") {
|
|
1582
|
-
const segments = string.split(separator);
|
|
1583
|
-
for (const segment of segments) {
|
|
1584
|
-
predict(segment, _lodash.default.indexOf(segments, segment), segments);
|
|
1585
|
-
}
|
|
1586
|
-
return segments.join(separator);
|
|
1587
|
-
}
|
|
1588
|
-
getSegmentsOfEachLine(string) {
|
|
1589
|
-
return string.split("\n");
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
/** 讓字串結尾必須是指定的 predicate, ex: `i'm good today?,,` => `i'm good today` */
|
|
1593
|
-
getNormalizedStringEndWith(string, predicate) {
|
|
1594
|
-
string = this.toCDB(string);
|
|
1595
|
-
predicate = this.toCDB(predicate);
|
|
1596
|
-
const after = _lodash.default.join(_lodash.default.dropRightWhile(string, each => !_lodash.default.isEqual(each, predicate)), "");
|
|
1597
|
-
return _lodash.default.isEmpty(after) ? string : after;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
/** 讓字串開頭不可以是 predicate, ex: `,, \n\t\s i'm good today?` => `i'm good today?` */
|
|
1601
|
-
getNormalizedStringNotStartWith(string, ...predicate) {
|
|
1602
|
-
string = this.toCDB(string);
|
|
1603
|
-
const after = _lodash.default.join(_lodash.default.dropWhile(string, each => this.has(predicate, each)), "");
|
|
1604
|
-
return _lodash.default.isEmpty(after) ? string : after;
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
/** 讓字串開頭不可以是 predicate, ex: `,, \n\t\s i'm good today?` => `\n\t\s i'm good today` */
|
|
1608
|
-
getNormalizedStringNotEndWith(string, ...predicate) {
|
|
1609
|
-
string = this.toCDB(string);
|
|
1610
|
-
const after = _lodash.default.join(_lodash.default.dropRightWhile(string, each => this.has(predicate, each)), "");
|
|
1611
|
-
return _lodash.default.isEmpty(after) ? string : after;
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
/** 取得 YYYY-MM-DD */
|
|
1615
|
-
getTodayTimeFormat(ts) {
|
|
1616
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format("YYYY-MM-DD");
|
|
1617
|
-
}
|
|
1618
|
-
getCustomFormatOfDatePresent(ts, format = "YY/MM") {
|
|
1619
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format(format);
|
|
1620
|
-
}
|
|
1621
|
-
getSimpleDateYYMMDDFormat(ts) {
|
|
1622
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format("YY/MM/DD");
|
|
1623
|
-
}
|
|
1624
|
-
getSimpleTimeYYMMDDHHmmFormat(ts) {
|
|
1625
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format("YY/MM/DD HH:mm");
|
|
1626
|
-
}
|
|
1627
|
-
getECPayCurrentTimeFormat(ts) {
|
|
1628
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format("YYYY/MM/DD HH:mm:ss");
|
|
1629
|
-
}
|
|
1630
|
-
getCurrentTimeFormatV2(ts) {
|
|
1631
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format("YYYY/MM/DD HH:mm:ss");
|
|
1632
|
-
}
|
|
1633
|
-
getCurrentTimeFormatYMDHM(ts) {
|
|
1634
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format("YYYY/MM/DD HH:mm");
|
|
1635
|
-
}
|
|
1636
|
-
getCurrentTimeFormatYMDHMS(ts) {
|
|
1637
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format("YYYY/MM/DD HH:mm:ss");
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
/** 取得 YYY-MM-DD-HH-mm-ss */
|
|
1641
|
-
getCurrentTimeFormat(ts) {
|
|
1642
|
-
return (0, _momentTimezone.default)(ts ? ts : this.getCurrentTimeStamp()).format("YYYY-MM-DD-HH-mm-ss");
|
|
1643
|
-
}
|
|
1644
|
-
getCurrentMillionSecTimeFormat(ts) {
|
|
1645
|
-
return (0, _momentTimezone.default)(ts ? ts : undefined).format("YYYY-MM-DD-HH-mm-ss-SSS");
|
|
1646
|
-
}
|
|
1647
|
-
isBetweenTimeStamp(target = this.getCurrentTimeStamp(), min, max) {
|
|
1648
|
-
return (0, _momentTimezone.default)(target).isBetween(min, max);
|
|
1649
|
-
}
|
|
1650
|
-
isBeforeTimeStamp(target = this.getCurrentTimeStamp(), time) {
|
|
1651
|
-
return (0, _momentTimezone.default)(target).isBefore(time);
|
|
1652
|
-
}
|
|
1653
|
-
isAfterTimeStamp(target = this.getCurrentTimeStamp(), time) {
|
|
1654
|
-
return (0, _momentTimezone.default)(target).isAfter(time);
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
/**
|
|
1658
|
-
* 根據地區語系與時區輸出 yyyy/MM/dd hh:mm 時間字串
|
|
1659
|
-
* @param {Date | number | string} ts - 時間戳記、日期物件或字串
|
|
1660
|
-
* @param {string} location - 語系地區代碼(如 'zh-TW'、'en-US')
|
|
1661
|
-
* @param {string} timezone - 時區(預設為 'Asia/Taipei')
|
|
1662
|
-
* @param {boolean} use24Hour - 是否使用 24 小時制(預設 true)
|
|
1663
|
-
* @returns {string}
|
|
1664
|
-
*/
|
|
1665
|
-
formatTimeByLocale(ts, location = "zh-TW", timezone = "Asia/Taipei", use24Hour = true) {
|
|
1666
|
-
// 設定 moment 語系
|
|
1667
|
-
_momentTimezone.default.locale(location);
|
|
1668
|
-
|
|
1669
|
-
// 轉換時區
|
|
1670
|
-
const m = (0, _momentTimezone.default)(ts).tz(timezone);
|
|
1671
|
-
|
|
1672
|
-
// 格式化字串
|
|
1673
|
-
const formatStr = use24Hour ? "YYYY/MM/DD HH:mm" : "YYYY/MM/DD hh:mm A";
|
|
1674
|
-
return m.format(formatStr);
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
/** 獲得 幾天後的timestamp 的概念 {months: 2,days:3} =>
|
|
1678
|
-
* ts => 1643434497341
|
|
1679
|
-
再利用 getCurrentTimeStamp(ts) => 2022-01-29
|
|
1680
|
-
*/
|
|
1681
|
-
getTimeStampWithConditions(param = {
|
|
1682
|
-
days: 0,
|
|
1683
|
-
months: 0,
|
|
1684
|
-
years: 0,
|
|
1685
|
-
minutes: 0,
|
|
1686
|
-
seconds: 0,
|
|
1687
|
-
hours: 0
|
|
1688
|
-
}, target = this.getCurrentTimeStamp()) {
|
|
1689
|
-
let base = (0, _momentTimezone.default)(target);
|
|
1690
|
-
for (const each in param) {
|
|
1691
|
-
const number = param[each];
|
|
1692
|
-
const unit = each;
|
|
1693
|
-
if (number > 0) {
|
|
1694
|
-
base = base.add(number, unit);
|
|
1695
|
-
}
|
|
1696
|
-
if (number < 0) {
|
|
1697
|
-
base = base.subtract(Math.abs(number), unit);
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
return base.valueOf();
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
/** 把 YYYY-MM-DD HH:mm:ss 轉換成 timestamp
|
|
1704
|
-
* 請注意 DD HH 之間有一個空格
|
|
1705
|
-
* */
|
|
1706
|
-
getTimeStampByStringFormat(string) {
|
|
1707
|
-
return this.getTimeStampFromSpecificFormat(string, "YYYY/MM/DD HH:mm:ss").valueOf();
|
|
1708
|
-
}
|
|
1709
|
-
getTimeStampFromSpecificFormat(string, format = "YYYY/MM/DD HH:mm:ss") {
|
|
1710
|
-
return (0, _momentTimezone.default)(string, format).valueOf();
|
|
1711
|
-
}
|
|
1712
|
-
getTimeStampFromECPayStringFormat(string) {
|
|
1713
|
-
return this.getTimeStampFromSpecificFormat(string, "YYYY/MM/DD HH:mm:ss").valueOf();
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
/** 要記住timestamp 可以轉換成西元時間(timestamp),或是期間(duration) 把duration time-stamp 轉成 02:13.445 */
|
|
1717
|
-
getTimeFormatOfDurationToMillionSecond(duration) {
|
|
1718
|
-
return _momentTimezone.default.utc(duration).format("HH小時mm分鐘ss秒SSS");
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
/** duration是兩個timestamp相減,把duration time-stamp 轉成 02:13,moment.utc 就是不會加八小時啦幹 */
|
|
1722
|
-
getTimeFormatOfDurationToSecond(duration) {
|
|
1723
|
-
return _momentTimezone.default.utc(duration).format("HH小時mm分鐘ss秒");
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
/** duration是兩個timestamp相減,把duration time-stamp 轉成 02:13,moment.utc 就是不會加八小時啦幹 , 為什麼對多1天 超怪 */
|
|
1727
|
-
getTimeFormatOfDurationToDay(duration) {
|
|
1728
|
-
return _momentTimezone.default.utc(duration).format("DD天HH小時mm分鐘ss秒");
|
|
1729
|
-
}
|
|
1730
|
-
getChineseTimeFormat(ts) {
|
|
1731
|
-
_momentTimezone.default.locale("zh-TW");
|
|
1732
|
-
return (0, _momentTimezone.default)(ts).format("LLLL");
|
|
1733
|
-
}
|
|
1734
|
-
getMinuteFormatOfDuration(ds) {
|
|
1735
|
-
return _momentTimezone.default.duration(ds).asMinutes();
|
|
1736
|
-
}
|
|
1737
|
-
getSecondFormatOfDuration(ds) {
|
|
1738
|
-
return _momentTimezone.default.duration(ds).asSeconds();
|
|
1739
|
-
}
|
|
1740
|
-
getDayFormatOfDuration(ds) {
|
|
1741
|
-
return _momentTimezone.default.duration(ds).asDays();
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
/** param可以是timeStamp,也可是date,或是string
|
|
1745
|
-
* timestamp : 1231231279
|
|
1746
|
-
* date :(new Date).now()
|
|
1747
|
-
* string: '2021-11-21'
|
|
1748
|
-
*
|
|
1749
|
-
* getDurationOfMillionSec('2022-01-21' || 123123112312 || (new Date).now())) */
|
|
1750
|
-
getDurationOfMillionSec(dateOrTimeStamp) {
|
|
1751
|
-
const currentTimestamp = this.getCurrentTimeStamp();
|
|
1752
|
-
const targetTimeStamp = (0, _momentTimezone.default)(dateOrTimeStamp).valueOf();
|
|
1753
|
-
const queue = _lodash.default.sortBy([{
|
|
1754
|
-
ts: currentTimestamp
|
|
1755
|
-
}, {
|
|
1756
|
-
ts: targetTimeStamp
|
|
1757
|
-
}], each => each.ts).map(each => each.ts);
|
|
1758
|
-
let after = (0, _momentTimezone.default)(queue.pop());
|
|
1759
|
-
let before = (0, _momentTimezone.default)(queue.shift()); // another date
|
|
1760
|
-
let duration = _momentTimezone.default.duration(after.diff(before));
|
|
1761
|
-
let ms = duration.asMilliseconds();
|
|
1762
|
-
return ms;
|
|
1763
|
-
}
|
|
1764
|
-
getCurrentTimeStamp() {
|
|
1765
|
-
return (0, _momentTimezone.default)().valueOf();
|
|
1766
|
-
}
|
|
1767
|
-
isStringContainInLines(context, key) {
|
|
1768
|
-
for (let each of _lodash.default.split(context, "\n")) {
|
|
1769
|
-
if (this.has(each, key)) return true;
|
|
1770
|
-
}
|
|
1771
|
-
return false;
|
|
1772
|
-
}
|
|
1773
|
-
camel(...words) {
|
|
1774
|
-
return _lodash.default.camelCase(words.join("_"));
|
|
1775
|
-
}
|
|
1776
|
-
upperCamel(...words) {
|
|
1777
|
-
return _lodash.default.upperFirst(this.camel(...words));
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
/**
|
|
1781
|
-
* [{key1:value1},{key2:values2}]
|
|
1782
|
-
* =>
|
|
1783
|
-
* {key1:value1,key2:value2}
|
|
1784
|
-
*
|
|
1785
|
-
* */
|
|
1786
|
-
array2Obj(array) {
|
|
1787
|
-
const obj = {};
|
|
1788
|
-
for (const each of array) {
|
|
1789
|
-
obj[`${this.getObjectKey(each)}`] = this.getObjectValue(each);
|
|
1790
|
-
}
|
|
1791
|
-
return obj;
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
/**
|
|
1795
|
-
*
|
|
1796
|
-
* [{name:'aaa',sign:2},{name:'aaa',sign:3},{name:'b',sign:4}] =>
|
|
1797
|
-
* {aaa:[{{name:'aaa',sign:2},{name:'aaa',sign:3}}], b:[{name:'b',sign:4}]}
|
|
1798
|
-
*/
|
|
1799
|
-
arrayToObjWith(array, predicate) {
|
|
1800
|
-
const obj = {};
|
|
1801
|
-
for (const item of array) {
|
|
1802
|
-
const key = predicate(item);
|
|
1803
|
-
const content = obj[key];
|
|
1804
|
-
if (content && _lodash.default.isArray(content)) {
|
|
1805
|
-
content.push(item);
|
|
1806
|
-
} else {
|
|
1807
|
-
obj[key] = [item];
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
|
-
return obj;
|
|
1811
|
-
}
|
|
1812
|
-
isEmptyString(string) {
|
|
1813
|
-
return _lodash.default.isEqual(_lodash.default.trim(string), "");
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
/** 放在後面的priority 越大 */
|
|
1817
|
-
mergeObject(...obj) {
|
|
1818
|
-
return _lodash.default.merge(...obj);
|
|
1819
|
-
}
|
|
1820
|
-
syncSetTimeout(func, ms, callback = () => {}) {
|
|
1821
|
-
(function sync(done) {
|
|
1822
|
-
if (!done) {
|
|
1823
|
-
setTimeout(function () {
|
|
1824
|
-
func();
|
|
1825
|
-
sync(true);
|
|
1826
|
-
}, ms);
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1829
|
-
callback();
|
|
1830
|
-
})();
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
/**
|
|
1834
|
-
* Merge multiple arrays of objects based on a specific identifier key.
|
|
1835
|
-
* If objects have the same identifier, they will be merged,
|
|
1836
|
-
* with properties from later arrays overwriting earlier ones.
|
|
1837
|
-
*
|
|
1838
|
-
* const list1 = [{ id: '123', name: 'david' }];
|
|
1839
|
-
* const list2 = [{ id: '123', age: 13 }];
|
|
1840
|
-
* const list3 = [{ id: '456', name: 'alice' }];
|
|
1841
|
-
* console.log(mergeArrayBy('id', list1, list2, list3)); //[ { href: '123', name: 'david', age: 13 },{ href: '456', name: 'alice' } ]
|
|
1842
|
-
*
|
|
1843
|
-
* @param {string} identifier - The object property used to identify and merge items. Default is 'id'.
|
|
1844
|
-
* @param {...Array<Object>} array - Multiple arrays containing objects to merge.
|
|
1845
|
-
* @returns {Array<Object>} A new array with merged objects based on the identifier.
|
|
1846
|
-
*/
|
|
1847
|
-
mergeArrayBy(identifier = "id", ...array) {
|
|
1848
|
-
return Object.values(array.flat().reduce((acc, item) => {
|
|
1849
|
-
if (item[identifier]) acc[item[identifier]] = {
|
|
1850
|
-
...(acc[item[identifier]] || {}),
|
|
1851
|
-
...item
|
|
1852
|
-
};
|
|
1853
|
-
return acc;
|
|
1854
|
-
}, {}));
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
/**
|
|
1858
|
-
* rootName : /free_marker/src/exam/web
|
|
1859
|
-
* pathName : /free_marker/src/exam/web/src/base/AlertDialog.js
|
|
1860
|
-
* return: /src/base/AlertDialog.js
|
|
1861
|
-
* */
|
|
1862
|
-
getRelativePath(pathName, rootName) {
|
|
1863
|
-
return _lodash.default.dropWhile(pathName, (each, index) => {
|
|
1864
|
-
return _lodash.default.isEqual(each, rootName[index]);
|
|
1865
|
-
}).join("");
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
/**
|
|
1869
|
-
* mutated;
|
|
1870
|
-
const arr = [0,1,2,3,4,5,6,7,8];
|
|
1871
|
-
dropItemsByIndex(arr,1,3);
|
|
1872
|
-
this.appendInfo(arr); [ 0, 4, 5, 6, 7, 8 ]
|
|
1873
|
-
*/
|
|
1874
|
-
dropItemsByIndex(array, from, end) {
|
|
1875
|
-
_lodash.default.remove(array, (value, index, array) => end >= index && index >= from);
|
|
1876
|
-
}
|
|
1877
|
-
isEven(n) {
|
|
1878
|
-
return n % 2 === 0;
|
|
1879
|
-
}
|
|
1880
|
-
isOdd(n) {
|
|
1881
|
-
return Math.abs(n % 2) === 1;
|
|
1882
|
-
}
|
|
1883
|
-
enrichZhTw() {
|
|
1884
|
-
_momentTimezone.default.locale("zh-tw", {
|
|
1885
|
-
months: "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),
|
|
1886
|
-
monthsShort: "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),
|
|
1887
|
-
weekdays: "星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),
|
|
1888
|
-
weekdaysShort: "周日_周一_周二_周三_周四_周五_周六".split("_"),
|
|
1889
|
-
weekdaysMin: "日_一_二_三_四_五_六".split("_"),
|
|
1890
|
-
longDateFormat: {
|
|
1891
|
-
LT: "Ah點mm分",
|
|
1892
|
-
LTS: "Ah點m分s秒",
|
|
1893
|
-
L: "YYYY-MM-DD",
|
|
1894
|
-
LL: "YYYY年MMMD日",
|
|
1895
|
-
LLL: "YYYY年MMMD日Ah點mm分",
|
|
1896
|
-
LLLL: "YYYY年MMMD日ddddAh點mm分",
|
|
1897
|
-
l: "YYYY-MM-DD",
|
|
1898
|
-
ll: "YYYY年MMMD日",
|
|
1899
|
-
lll: "YYYY年MMMD日Ah點mm分",
|
|
1900
|
-
llll: "YYYY年MMMD日ddddAh點mm分"
|
|
1901
|
-
},
|
|
1902
|
-
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
|
|
1903
|
-
meridiemHour: function (h, meridiem) {
|
|
1904
|
-
let hour = h;
|
|
1905
|
-
if (hour === 12) {
|
|
1906
|
-
hour = 0;
|
|
1907
|
-
}
|
|
1908
|
-
if (meridiem === "凌晨" || meridiem === "早上" || meridiem === "上午") {
|
|
1909
|
-
return hour;
|
|
1910
|
-
} else if (meridiem === "下午" || meridiem === "晚上") {
|
|
1911
|
-
return hour + 12;
|
|
1912
|
-
} else {
|
|
1913
|
-
// '中午'
|
|
1914
|
-
return hour >= 11 ? hour : hour + 12;
|
|
1915
|
-
}
|
|
1916
|
-
},
|
|
1917
|
-
meridiem: function (hour, minute, isLower) {
|
|
1918
|
-
const hm = hour * 100 + minute;
|
|
1919
|
-
if (hm < 600) {
|
|
1920
|
-
return "凌晨";
|
|
1921
|
-
} else if (hm < 900) {
|
|
1922
|
-
return "早上";
|
|
1923
|
-
} else if (hm < 1130) {
|
|
1924
|
-
return "上午";
|
|
1925
|
-
} else if (hm < 1230) {
|
|
1926
|
-
return "中午";
|
|
1927
|
-
} else if (hm < 1800) {
|
|
1928
|
-
return "下午";
|
|
1929
|
-
} else {
|
|
1930
|
-
return "晚上";
|
|
1931
|
-
}
|
|
1932
|
-
},
|
|
1933
|
-
calendar: {
|
|
1934
|
-
sameDay: function () {
|
|
1935
|
-
return this.minutes() === 0 ? "[今天]Ah[點整]" : "[今天]LT";
|
|
1936
|
-
},
|
|
1937
|
-
nextDay: function () {
|
|
1938
|
-
return this.minutes() === 0 ? "[明天]Ah[點整]" : "[明天]LT";
|
|
1939
|
-
},
|
|
1940
|
-
lastDay: function () {
|
|
1941
|
-
return this.minutes() === 0 ? "[昨天]Ah[點整]" : "[昨天]LT";
|
|
1942
|
-
},
|
|
1943
|
-
nextWeek: function () {
|
|
1944
|
-
let startOfWeek, prefix;
|
|
1945
|
-
startOfWeek = (0, _momentTimezone.default)().startOf("week");
|
|
1946
|
-
prefix = this.diff(startOfWeek, "days") >= 7 ? "[下]" : "[本]";
|
|
1947
|
-
return this.minutes() === 0 ? prefix + "dddA點整" : prefix + "dddAh點mm";
|
|
1948
|
-
},
|
|
1949
|
-
lastWeek: function () {
|
|
1950
|
-
let startOfWeek, prefix;
|
|
1951
|
-
startOfWeek = (0, _momentTimezone.default)().startOf("week");
|
|
1952
|
-
prefix = this.unix() < startOfWeek.unix() ? "[上]" : "[本]";
|
|
1953
|
-
return this.minutes() === 0 ? prefix + "dddAh點整" : prefix + "dddAh點mm";
|
|
1954
|
-
},
|
|
1955
|
-
sameElse: "LL"
|
|
1956
|
-
},
|
|
1957
|
-
ordinalParse: /\d{1,2}(日|月|周)/,
|
|
1958
|
-
ordinal: function (number, period) {
|
|
1959
|
-
switch (period) {
|
|
1960
|
-
case "d":
|
|
1961
|
-
case "D":
|
|
1962
|
-
case "DDD":
|
|
1963
|
-
return number + "日";
|
|
1964
|
-
case "M":
|
|
1965
|
-
return number + "月";
|
|
1966
|
-
case "w":
|
|
1967
|
-
case "W":
|
|
1968
|
-
return number + "周";
|
|
1969
|
-
default:
|
|
1970
|
-
return number;
|
|
1971
|
-
}
|
|
1972
|
-
},
|
|
1973
|
-
relativeTime: {
|
|
1974
|
-
future: "%s内",
|
|
1975
|
-
past: "%s前",
|
|
1976
|
-
s: "幾秒",
|
|
1977
|
-
m: "1 分鐘",
|
|
1978
|
-
mm: "%d 分鐘",
|
|
1979
|
-
h: "1 小時",
|
|
1980
|
-
hh: "%d 小時",
|
|
1981
|
-
d: "1 天",
|
|
1982
|
-
dd: "%d 天",
|
|
1983
|
-
M: "1 個月",
|
|
1984
|
-
MM: "%d 个月",
|
|
1985
|
-
y: "1 年",
|
|
1986
|
-
yy: "%d 年"
|
|
1987
|
-
},
|
|
1988
|
-
week: {
|
|
1989
|
-
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
|
|
1990
|
-
dow: 1,
|
|
1991
|
-
// Monday is the first day of the week.
|
|
1992
|
-
doy: 4 // The week that contains Jan 4th is the first week of the year.
|
|
1993
|
-
}
|
|
1994
|
-
});
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
/** react js Util */
|
|
1998
|
-
getVisibleOrHidden(judgement) {
|
|
1999
|
-
return {
|
|
2000
|
-
visibility: judgement ? "visible" : "hidden"
|
|
2001
|
-
};
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
/**
|
|
2005
|
-
* 將百分比轉換為浮點數
|
|
2006
|
-
* @param {string} percentage - 以百分比表示的字串,例如 "50%" 或 "12.5%"
|
|
2007
|
-
* @returns {number} - 對應的浮點數,例如 0.5 或 0.125
|
|
2008
|
-
*/
|
|
2009
|
-
getNumberOfPercentageToFloat(percentage) {
|
|
2010
|
-
// 移除百分比符號
|
|
2011
|
-
let cleanedPercentage = percentage.replace("%", "");
|
|
2012
|
-
// 將字串轉換為浮點數並除以 100
|
|
2013
|
-
let floatNumber = parseFloat(cleanedPercentage) / 100;
|
|
2014
|
-
return floatNumber;
|
|
2015
|
-
}
|
|
2016
|
-
getVisibleOrNone(judgement, flex = false) {
|
|
2017
|
-
return {
|
|
2018
|
-
display: judgement ? flex ? "flex" : "inherit" : "none"
|
|
2019
|
-
};
|
|
2020
|
-
}
|
|
2021
|
-
stringToInteger(string) {
|
|
2022
|
-
string = _lodash.default.toUpper(string);
|
|
2023
|
-
switch (string) {
|
|
2024
|
-
case "A":
|
|
2025
|
-
return 0;
|
|
2026
|
-
case "B":
|
|
2027
|
-
return 1;
|
|
2028
|
-
case "C":
|
|
2029
|
-
return 2;
|
|
2030
|
-
case "D":
|
|
2031
|
-
return 3;
|
|
2032
|
-
case "E":
|
|
2033
|
-
return 4;
|
|
2034
|
-
case "F":
|
|
2035
|
-
return 5;
|
|
2036
|
-
case "G":
|
|
2037
|
-
return 6;
|
|
2038
|
-
case "H":
|
|
2039
|
-
return 7;
|
|
2040
|
-
case "I":
|
|
2041
|
-
return 8;
|
|
2042
|
-
case "J":
|
|
2043
|
-
return 9;
|
|
2044
|
-
case "K":
|
|
2045
|
-
return 10;
|
|
2046
|
-
case "L":
|
|
2047
|
-
return 11;
|
|
2048
|
-
case "M":
|
|
2049
|
-
return 12;
|
|
2050
|
-
case "N":
|
|
2051
|
-
return 13;
|
|
2052
|
-
default:
|
|
2053
|
-
return 101;
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
integerToString(integer) {
|
|
2057
|
-
switch (integer) {
|
|
2058
|
-
case 0:
|
|
2059
|
-
return "A";
|
|
2060
|
-
case 1:
|
|
2061
|
-
return "B";
|
|
2062
|
-
case 2:
|
|
2063
|
-
return "C";
|
|
2064
|
-
case 3:
|
|
2065
|
-
return "D";
|
|
2066
|
-
case 4:
|
|
2067
|
-
return "E";
|
|
2068
|
-
case 5:
|
|
2069
|
-
return "F";
|
|
2070
|
-
case 6:
|
|
2071
|
-
return "G";
|
|
2072
|
-
case 7:
|
|
2073
|
-
return "H";
|
|
2074
|
-
case 8:
|
|
2075
|
-
return "I";
|
|
2076
|
-
case 9:
|
|
2077
|
-
return "J";
|
|
2078
|
-
case 10:
|
|
2079
|
-
return "K";
|
|
2080
|
-
case 11:
|
|
2081
|
-
return "L";
|
|
2082
|
-
case 12:
|
|
2083
|
-
return "M";
|
|
2084
|
-
case 13:
|
|
2085
|
-
return "N";
|
|
2086
|
-
default:
|
|
2087
|
-
return "Z";
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
/**
|
|
2092
|
-
* const sample = [{name: 'a'}, {name: 'b'}];
|
|
2093
|
-
*
|
|
2094
|
-
* rules => {to:'newKeyName', from: 'name', func: (stmt) => stmt}
|
|
2095
|
-
* to指的是新的屬性名稱, from指的就是sample物件裏面要被取代的原屬性(這裡是指name),如果屬性的的value(string,number).表示each的內容就是value. func就可以把再包一層邏輯
|
|
2096
|
-
*
|
|
2097
|
-
* sample:
|
|
2098
|
-
* const sample = [{name: 'a'}, {name: 'b'}];
|
|
2099
|
-
* console.log(util.toObjectMap(sample, {to: 'newName', from: 'name',func:(p) => (p+'yaya')}));
|
|
2100
|
-
* result : [ { newName: 'ayaya' }, { newName: 'byaya' } ]
|
|
2101
|
-
*/
|
|
2102
|
-
toObjectMap(array, ...rules) {
|
|
2103
|
-
const newbies = [];
|
|
2104
|
-
for (const each of array) {
|
|
2105
|
-
const object = {};
|
|
2106
|
-
for (const rule of rules) {
|
|
2107
|
-
const func = rule.func ? rule.func : stmt => stmt;
|
|
2108
|
-
object[rule.to] = this.isUndefinedNullEmpty(rule.from) || !_lodash.default.isObject(each) ? func(each) : func(each[rule.from]);
|
|
2109
|
-
}
|
|
2110
|
-
newbies.push(object);
|
|
2111
|
-
}
|
|
2112
|
-
return newbies;
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
/**
|
|
2116
|
-
* sample:
|
|
2117
|
-
const array = [{aa: '1'},{ aa: '2'}, {aa: '3'}];
|
|
2118
|
-
const object = {aa: '1', bb: '2', cc: '3'};
|
|
2119
|
-
util.exeAll(object,(each) => each + 1)
|
|
2120
|
-
util.exeAll(array,(each) => {each.aa = each.aa + 1});
|
|
2121
|
-
console.log(object); // { aa: '11', bb: '21', cc: '31' }
|
|
2122
|
-
console.log(array); // [ { aa: '11' }, { aa: '21' }, { aa: '31' } ]
|
|
2123
|
-
* 把collection 裏面的物件執行一下,會mutate本身*/
|
|
2124
|
-
exeAll(collection, ...funcs) {
|
|
2125
|
-
if (_lodash.default.isArray(collection)) {
|
|
2126
|
-
for (const each of collection) {
|
|
2127
|
-
for (const func of funcs) {
|
|
2128
|
-
func(each);
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
/** 陣列專屬邏輯 */
|
|
2132
|
-
} else if (_lodash.default.isObject(collection)) {
|
|
2133
|
-
for (const each in collection) {
|
|
2134
|
-
for (const func of funcs) {
|
|
2135
|
-
collection[each] = func(collection[each]);
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
/** 物件專屬邏輯 */
|
|
2139
|
-
} else {
|
|
2140
|
-
throw new _exceptioner.default(9999, `7841212 type can't be array or object`);
|
|
2141
|
-
}
|
|
2142
|
-
return collection;
|
|
2143
|
-
}
|
|
2144
|
-
getObjectWhile(major, minor, predicate = target => true) {
|
|
2145
|
-
const collection = {};
|
|
2146
|
-
for (const key in major) {
|
|
2147
|
-
if (predicate(major, minor, key)) {
|
|
2148
|
-
collection[key] = major[key];
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
return collection;
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
/** 找出兩個object,相同的屬性
|
|
2155
|
-
sample:
|
|
2156
|
-
const obj1 = {a:1,b:4,c:3};
|
|
2157
|
-
const obj2 = {b:3};
|
|
2158
|
-
console.log(util.getIntersectionObject(obj1,obj2)) => { b: 4 }
|
|
2159
|
-
*/
|
|
2160
|
-
getIntersectionObject(objOfMajor, objOfMinor) {
|
|
2161
|
-
return this.getObjectWhile(objOfMajor, objOfMinor, (major, minor, key) => minor[key] !== undefined);
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
/** 找出兩個object,相同的屬性
|
|
2165
|
-
sample:
|
|
2166
|
-
const obj1 = {a:1,b:4,c:3};
|
|
2167
|
-
const obj2 = {b:3};
|
|
2168
|
-
console.log(util.getIntersectionObject(obj1,obj2)) => { a: 1, c: 3 }
|
|
2169
|
-
*/
|
|
2170
|
-
getDifferenceObject(objOfMajor, objOfMinor) {
|
|
2171
|
-
return this.getObjectWhile(objOfMajor, objOfMinor, (major, minor, key) => minor[key] === undefined);
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
/**
|
|
2175
|
-
*
|
|
2176
|
-
const obj1 = {b:4,c:2};
|
|
2177
|
-
const obj2 = {b:4,c:3};
|
|
2178
|
-
const obj3 = {a:1,b:4,c:3};
|
|
2179
|
-
console.log(util.isObjectContainAndEqual(obj1,obj3)) false
|
|
2180
|
-
console.log(util.isObjectContainAndEqual(obj1,obj3)) true
|
|
2181
|
-
targetObject 是數量比較小那個
|
|
2182
|
-
*/
|
|
2183
|
-
isObjectContainAndEqual(targetObject, mainObject) {
|
|
2184
|
-
let equal = true;
|
|
2185
|
-
for (const key in targetObject) {
|
|
2186
|
-
if (mainObject[key] === undefined || mainObject[key] !== targetObject[key]) {
|
|
2187
|
-
equal = false;
|
|
2188
|
-
break;
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
return equal;
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
/** 把 /a/v/c/d => /a/v/c/ */
|
|
2195
|
-
getStringOfPop(string, separator) {
|
|
2196
|
-
if (!_lodash.default.isString(string)) {
|
|
2197
|
-
throw new _exceptioner.default(9999, `445115,type should be string but ==> ${typeof string}`);
|
|
2198
|
-
}
|
|
2199
|
-
const segments = string.split(separator);
|
|
2200
|
-
segments.pop();
|
|
2201
|
-
return segments.join(separator);
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
/** 把 /a/v/c/d => /v/c/d */
|
|
2205
|
-
getStringOfShift(string, separator) {
|
|
2206
|
-
if (!_lodash.default.isString(string)) {
|
|
2207
|
-
throw new _exceptioner.default(9999, `445116,type should be string but ==> ${typeof string}`);
|
|
2208
|
-
}
|
|
2209
|
-
const segments = string.split(separator);
|
|
2210
|
-
segments.shift();
|
|
2211
|
-
return segments.join(separator);
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
|
-
/**
|
|
2215
|
-
* array = [{name:'david',id:'kfgijifd'},{name:'serena',id:'kdffof'}....]
|
|
2216
|
-
* attrKeyOfPK = 'id'
|
|
2217
|
-
* result => { kfgijifd: {name:'david',id:'kfgijifd'}, kdffof:{name:'serena',id:'kdffof'} }
|
|
2218
|
-
* */
|
|
2219
|
-
toObjectWithAttributeKey(array, attrKeyOfPK) {
|
|
2220
|
-
const object = {};
|
|
2221
|
-
for (const each of array) {
|
|
2222
|
-
const pk = each[attrKeyOfPK];
|
|
2223
|
-
if (this.isUndefinedNullEmpty(pk)) {
|
|
2224
|
-
throw new _exceptioner.default(9999, `48157232 pk can't be empty => '${pk}'`);
|
|
2225
|
-
}
|
|
2226
|
-
object[pk] = each;
|
|
2227
|
-
}
|
|
2228
|
-
return object;
|
|
2229
|
-
}
|
|
2230
|
-
getObjectOfArraySpecifyAttr(array, attr) {
|
|
2231
|
-
return this.toObjectWithAttributeKey(array, attr);
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
/**
|
|
2235
|
-
* 用來檢查string是否包含字元
|
|
2236
|
-
* string = '|C G/B|'
|
|
2237
|
-
* signs = ['/','$']
|
|
2238
|
-
* return ==> {exist:true,sign:'/'}
|
|
2239
|
-
*
|
|
2240
|
-
* @param string
|
|
2241
|
-
* @param signs
|
|
2242
|
-
* @returns {{exists: boolean}|{sign: *, exists: boolean}}
|
|
2243
|
-
*/
|
|
2244
|
-
getStateOfStringContainsSign(string, ...signs) {
|
|
2245
|
-
for (const sign of signs) {
|
|
2246
|
-
if (this.has(string, sign)) {
|
|
2247
|
-
return {
|
|
2248
|
-
exists: true,
|
|
2249
|
-
sign
|
|
2250
|
-
};
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
return {
|
|
2254
|
-
exists: false
|
|
2255
|
-
};
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2258
|
-
/** others returns [{logic:true|false,message:'oops'}]
|
|
2259
|
-
* */
|
|
2260
|
-
constraintOfParam(collection, type, ...others) {
|
|
2261
|
-
let result = false;
|
|
2262
|
-
const validOfOthersCondition = _lodash.default.isEmpty(others) ? true : this.and(...others.map(each => each.logic));
|
|
2263
|
-
switch (type) {
|
|
2264
|
-
case "array":
|
|
2265
|
-
if (_lodash.default.isArray(collection) && validOfOthersCondition) result = true;
|
|
2266
|
-
break;
|
|
2267
|
-
case "object":
|
|
2268
|
-
if (_lodash.default.isObject(collection) && validOfOthersCondition) result = true;
|
|
2269
|
-
break;
|
|
2270
|
-
case "string":
|
|
2271
|
-
if (_lodash.default.isString(collection) && validOfOthersCondition) result = true;
|
|
2272
|
-
break;
|
|
2273
|
-
case "number":
|
|
2274
|
-
if (_lodash.default.isNumber(collection) && validOfOthersCondition) result = true;
|
|
2275
|
-
break;
|
|
2276
|
-
case "other":
|
|
2277
|
-
if (validOfOthersCondition) return true;
|
|
2278
|
-
}
|
|
2279
|
-
const stringOfRules = _lodash.default.isEmpty(others) ? "" : `, ${others.map(each => each.message).join(" | ")}`;
|
|
2280
|
-
if (result === false) {
|
|
2281
|
-
throw new _exceptioner.default(9999, `7474423 type should be ${type} but get '${typeof type}' ${stringOfRules} `);
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
/**
|
|
2286
|
-
const array = _.range(0, 50).map((each) => `index Of each`);
|
|
2287
|
-
console.log('origin: ==> ', array.length) //origin: ==> 50
|
|
2288
|
-
const result = util.getSliceArrayWithMutate(array, 10);
|
|
2289
|
-
console.log('after: ==> ', result.length, ' | ', array.length) //after: ==> 10 | 40
|
|
2290
|
-
*/
|
|
2291
|
-
getSliceArrayWithMutate(array, n) {
|
|
2292
|
-
const slice = _lodash.default.remove(array, (each, index) => index < n);
|
|
2293
|
-
return slice;
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
/**
|
|
2297
|
-
* const array1 = [1, 2, 3, 4, 5];
|
|
2298
|
-
* const array2 = [3, 4, 5, 6, 7];
|
|
2299
|
-
* Output: [1, 2]
|
|
2300
|
-
* */
|
|
2301
|
-
getArrayOfInteraction(one, two) {
|
|
2302
|
-
return one.filter(element => !two.includes(element));
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
/**
|
|
2306
|
-
*
|
|
2307
|
-
* 把array裏面的'指定index' 移動到 '特定index'
|
|
2308
|
-
*
|
|
2309
|
-
const array = [0,1,2,3,4,5,6,7];
|
|
2310
|
-
console.log(util.getArrayOfMoveToSpecificIndex(array,1,0));const array = [0,1,2,3,4,5,6,7];
|
|
2311
|
-
console.log(util.getArrayOfMoveToSpecificIndex(array,1,0));
|
|
2312
|
-
[
|
|
2313
|
-
1, 0, 2, 3,
|
|
2314
|
-
4, 5, 6, 7
|
|
2315
|
-
]
|
|
2316
|
-
|-------如果有paginate, 有可能讓功能錯亂-------|
|
|
2317
|
-
*/
|
|
2318
|
-
getArrayOfMoveToSpecificIndex(array, from, to) {
|
|
2319
|
-
if (!Array.isArray(array)) {
|
|
2320
|
-
throw new Error("First argument must be an array.");
|
|
2321
|
-
}
|
|
2322
|
-
const length = array.length;
|
|
2323
|
-
// 驗證索引範圍
|
|
2324
|
-
if (from < 0 || from >= length || to < 0 || to >= length) {
|
|
2325
|
-
console.warn("Invalid 'from' or 'to' index for getArrayOfMoveToSpecificIndexOptimized.");
|
|
2326
|
-
// 可以選擇拋出錯誤或返回原陣列的副本
|
|
2327
|
-
// throw new RangeError("Index out of bounds");
|
|
2328
|
-
return [...array]; // 返回副本
|
|
2329
|
-
}
|
|
2330
|
-
if (from === to) {
|
|
2331
|
-
return [...array]; // 位置相同,無需移動,返回副本
|
|
2332
|
-
}
|
|
2333
|
-
const copy = [...array]; // 創建副本
|
|
2334
|
-
const [item] = copy.splice(from, 1); // 從副本中移除元素
|
|
2335
|
-
copy.splice(to, 0, item); // 將元素插入到副本的新位置
|
|
2336
|
-
return copy; // 返回修改後的副本
|
|
2337
|
-
}
|
|
2338
|
-
|
|
2339
|
-
/** 把array裏面的項目移動到指定的index
|
|
2340
|
-
*
|
|
2341
|
-
* const array = ['a','b','c','d'];
|
|
2342
|
-
console.log(util.getArrayOfMoveItemToSpecificIndex(array,array[1],0));
|
|
2343
|
-
//[ 'b', 'a', 'c', 'd' ]
|
|
2344
|
-
* */
|
|
2345
|
-
getArrayOfMoveItemToSpecificIndex(array, item, indexOfDestination) {
|
|
2346
|
-
const indexOfItem = _lodash.default.indexOf(array, item);
|
|
2347
|
-
return this.getArrayOfMoveToSpecificIndex(array, indexOfItem, indexOfDestination);
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2350
|
-
/**
|
|
2351
|
-
* 把指定的array item 放到頭尾
|
|
2352
|
-
* const array = ['a','b','c','d'];
|
|
2353
|
-
* console.log(util.getArrayOfMoveSpecificItemToAside(array,array[1]));
|
|
2354
|
-
*[ 'a', 'c', 'd', 'b' ]
|
|
2355
|
-
*/
|
|
2356
|
-
getArrayOfMoveSpecificItemToAside(array, item, toTail = true) {
|
|
2357
|
-
const indexOfItem = _lodash.default.indexOf(array, item);
|
|
2358
|
-
return this.getArrayOfMoveSpecificIndexToAside(array, indexOfItem, toTail);
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
/** 把指定的index放到頭尾
|
|
2362
|
-
* const array = ['a','b','c','d'];
|
|
2363
|
-
console.log(util.getArrayOfMoveSpecificIndexToAside(array,3,false));
|
|
2364
|
-
[ 'd', 'a', 'b', 'c' ]
|
|
2365
|
-
**/
|
|
2366
|
-
getArrayOfMoveSpecificIndexToAside(array, index, toTail = true) {
|
|
2367
|
-
const indexOfLast = _lodash.default.size(array) - 1;
|
|
2368
|
-
return this.getArrayOfMoveToSpecificIndex(array, index, toTail ? indexOfLast : 0);
|
|
2369
|
-
}
|
|
2370
|
-
getECPayCheckMacValue(data, hashKey = "5294y06JbISpM5x9", hashIV = "v77hoKGq4kWxNNIS") {
|
|
2371
|
-
const clone = _lodash.default.cloneDeep(data);
|
|
2372
|
-
delete clone.CheckMacValue;
|
|
2373
|
-
const keys = Object.keys(clone).sort((l, r) => l > r ? 1 : -1);
|
|
2374
|
-
let checkValue = "";
|
|
2375
|
-
for (const key of keys) {
|
|
2376
|
-
checkValue += `${key}=${clone[key]}&`;
|
|
2377
|
-
}
|
|
2378
|
-
checkValue = `HashKey=${hashKey}&${checkValue}HashIV=${hashIV}`; // There is already an & in the end of checkValue
|
|
2379
|
-
checkValue = encodeURIComponent(checkValue).toLowerCase();
|
|
2380
|
-
checkValue = checkValue.replace(/%20/g, "+").replace(/%2d/g, "-").replace(/%5f/g, "_").replace(/%2e/g, ".").replace(/%21/g, "!").replace(/%2a/g, "*").replace(/%28/g, "(").replace(/%29/g, ")").replace(/%20/g, "+");
|
|
2381
|
-
|
|
2382
|
-
/** checkValue = Crypto.createHash('sha256').update(checkValue).digest('hex');
|
|
2383
|
-
* 之前用crypto做出來的,後來crypto-browsify多年沒有更新,所以都要用CryptoJS處理 2024/03/12
|
|
2384
|
-
* */
|
|
2385
|
-
return _lodash.default.toUpper(_cryptoJs.default.SHA256(checkValue).toString(_cryptoJs.default.enc.Hex));
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
/** 把一段html文字轉換成類似document的結構 處理後再回傳文字
|
|
2389
|
-
const result = utiller.getStringOfHandleHtml(
|
|
2390
|
-
'<form id="_form_aiochk" action="https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5" method="post"><input type="hidden" name="MerchantTradeNo" id="MerchantTradeNo" value="sO6E2IilSGYpCChDqrI2" /><input type="hidden" name="MerchantTradeDate" id="MerchantTradeDate" value="2022/07/02 05:16:32" />' +
|
|
2391
|
-
'<input type="hidden" name="TotalAmount" id="TotalAmount" value="350" /><input type="hidden" name="TradeDesc" id="TradeDesc" value="綠界第三方支付(明悅科技-線上支付)" /><input type="hidden" name="ItemName" id="ItemName" value="iphone13 pro x 2 = 200 元#iphone11 x 3 = 150 元#總價 350 元##※備註: 無備註內容" /><input type="hidden" name="ReturnURL" id="ReturnURL" value="https://us-central1-davidtu-dev.cloudfunctions.net/confirmedByByECPay" /><input type="hidden" name="ClientBackURL" id="ClientBackURL" value="https://www.google.com/" /><input type="hidden" name="ExpireDate" id="ExpireDate" value="1" /><input type="hidden" name="PaymentInfoURL" id="PaymentInfoURL" value="https://us-central1-davidtu-dev.cloudfunctions.net/paymentInfoByECPay" /><input type="hidden" name="ChoosePayment" id="ChoosePayment" value="ALL" /><input type="hidden" name="PlatformID" id="PlatformID" value="" /><input type="hidden" name="MerchantID" id="MerchantID" value="2000132" /><input type="hidden" name="InvoiceMark" id="InvoiceMark" value="N" /><input type="hidden" name="IgnorePayment" id="IgnorePayment" value="BARCODE#AndroidPay#ApplePay" /><input type="hidden" name="DeviceSource" id="DeviceSource" value="" /><input type="hidden" name="EncryptType" id="EncryptType" value="1" /><input type="hidden" name="PaymentType" id="PaymentType" value="aio" />' +
|
|
2392
|
-
'<input type="hidden" name="CheckMacValue" id="CheckMacValue" value="D55E9E48C6AB83C063E0E13AD1B8C2EE8FA6547A7D7FCB33860B532E97D808BC" /><script type="text/javascript">document.getElementById("_form_aiochk").submit();</script></form>'
|
|
2393
|
-
,(document) => {
|
|
2394
|
-
const element = document.getElementById('CheckMacValue');
|
|
2395
|
-
element.setAttribute('value', '123456');
|
|
2396
|
-
})
|
|
2397
|
-
*/
|
|
2398
|
-
getStringOfHandledHtml(htmlString, predicate = document => {
|
|
2399
|
-
console.log(document);
|
|
2400
|
-
}) {
|
|
2401
|
-
const document = (0, _nodeHtmlParser.parse)(htmlString);
|
|
2402
|
-
predicate(document);
|
|
2403
|
-
return document.toString();
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
/** 會有物件在比較優先權,例如option = {id:1,photo:'url'} choice = {id, photo:'url'}
|
|
2407
|
-
*
|
|
2408
|
-
* const selected = getSpecifyObjectBy([option.photo,choice.photo],(string) => !_.isEmpty(string))
|
|
2409
|
-
* */
|
|
2410
|
-
getSpecifyObjectBy(array, predicate) {
|
|
2411
|
-
for (const item of array) {
|
|
2412
|
-
if (predicate(item)) return item;
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2416
|
-
/**
|
|
2417
|
-
* @param content = object
|
|
2418
|
-
* @param rules {KEY:predicate} | 'KEY', rules如果只放字串, rule = KEY就代表這個欄位不得為isUndefinedEmpty(), 如果是物件 => {key:predicate}
|
|
2419
|
-
* @param idOfError 用在每個呼叫的method, 有個stack trace的概念
|
|
2420
|
-
*
|
|
2421
|
-
*
|
|
2422
|
-
* console.log(utiller.validatePayloadObjectValid({a: 3, b: 4}, ['a',{b:(value) => value > 5}]));
|
|
2423
|
-
* //ATTRIBUTE:'b' is not valid of custom rule
|
|
2424
|
-
*
|
|
2425
|
-
* utiller.validatePayloadObjectValid({id: 'djksaio', num: 3, items: [1, 2, 3]},
|
|
2426
|
-
* [
|
|
2427
|
-
* {'id': (value) => _.isString(value)},
|
|
2428
|
-
* {'num': (v) => _.isNumber(v)},
|
|
2429
|
-
* {items: (v) => _.isArray(v)}
|
|
2430
|
-
* ])
|
|
2431
|
-
* // =>true
|
|
2432
|
-
*/
|
|
2433
|
-
validatePayloadObjectValid(content, rules = [], idOfError = this.getRandomHash(10)) {
|
|
2434
|
-
if (this.isUndefinedNullEmpty(content)) {
|
|
2435
|
-
throw new _exceptioner.default(9999, `${idOfError} content(pay-load) is undefined || empty`);
|
|
2436
|
-
}
|
|
2437
|
-
for (const rule of rules) {
|
|
2438
|
-
if (_lodash.default.isString(rule)) {
|
|
2439
|
-
if (this.isUndefinedNullEmpty(content[rule])) {
|
|
2440
|
-
throw new _exceptioner.default(9999, `${idOfError} ATTRIBUTE:'${rule}' is not Exist`);
|
|
2441
|
-
}
|
|
2442
|
-
} else if (_lodash.default.isObject(rule)) {
|
|
2443
|
-
const key = this.getObjectKey(rule);
|
|
2444
|
-
const predicate = this.getObjectValue(rule);
|
|
2445
|
-
if (!predicate(content[key])) {
|
|
2446
|
-
throw new _exceptioner.default(9999, `${idOfError} ATTRIBUTE:'${key}' is not valid of custom rule`);
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
}
|
|
2450
|
-
return true;
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
/**
|
|
2454
|
-
* 做個總和
|
|
2455
|
-
*
|
|
2456
|
-
const result = utiller.getArrayOfSummarizeBy([{name:'david',count:5},{name:'nina',count:3},{name:'david',count:3},{name:'joe',count:3},{name:'joe',count:4}]
|
|
2457
|
-
,'name','count');
|
|
2458
|
-
console.log(result);
|
|
2459
|
-
[
|
|
2460
|
-
{ name: 'david', count: 8 },
|
|
2461
|
-
{ name: 'nina', count: 3 },
|
|
2462
|
-
{ name: 'joe', count: 7 }
|
|
2463
|
-
]
|
|
2464
|
-
*
|
|
2465
|
-
*/
|
|
2466
|
-
getArrayOfSummarizeBy(array, keyOfId, keyOfSum) {
|
|
2467
|
-
const obj = {};
|
|
2468
|
-
for (const item of array) {
|
|
2469
|
-
const key = item[keyOfId];
|
|
2470
|
-
if (obj[key] !== undefined) {
|
|
2471
|
-
obj[key] = obj[key] + item[keyOfSum];
|
|
2472
|
-
} else {
|
|
2473
|
-
obj[key] = item[keyOfSum];
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
const items = [];
|
|
2477
|
-
for (const key in obj) {
|
|
2478
|
-
const _obj = {};
|
|
2479
|
-
_obj[keyOfId] = key;
|
|
2480
|
-
_obj[keyOfSum] = obj[key];
|
|
2481
|
-
items.push(_obj);
|
|
2482
|
-
}
|
|
2483
|
-
return items;
|
|
2484
|
-
}
|
|
2485
|
-
getHeadStringSplitBy(string, sign = this.getSeparatorOfUnique()) {
|
|
2486
|
-
return _lodash.default.split(string, sign).shift();
|
|
2487
|
-
}
|
|
2488
|
-
getTailStringSplitBy(string, sign = this.getSeparatorOfUnique()) {
|
|
2489
|
-
return _lodash.default.split(string, sign).pop();
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
/** 把array根據indexes分割成slices(array)
|
|
2493
|
-
* array = [0,1,2,3,4,5,6,7]
|
|
2494
|
-
* indexes = [0,3,5,7];
|
|
2495
|
-
* return [... [array1(0,3) ],[array2(3,5)],[array3(5,7)] ],
|
|
2496
|
-
* */
|
|
2497
|
-
getSlicesByIndexes(array = [], indexes = []) {
|
|
2498
|
-
const slices = [];
|
|
2499
|
-
_lodash.default.each(indexes, (each, index, arrayOfIndexes) => {
|
|
2500
|
-
if (_lodash.default.isEqual(index, indexes.length - 1)) return false;
|
|
2501
|
-
const slice = _lodash.default.slice(array, each, indexes[index + 1]);
|
|
2502
|
-
slices.push(slice);
|
|
2503
|
-
});
|
|
2504
|
-
return slices;
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
/** 用_.findIndex(比較內文的方式) 去找出array裡所有符合條件的
|
|
2508
|
-
* array = [-2, -1, 65, -4, 77]
|
|
2509
|
-
* predicate = (item) => item > 1;
|
|
2510
|
-
* return [3,5]
|
|
2511
|
-
* */
|
|
2512
|
-
findIndexes(array, predicate) {
|
|
2513
|
-
const indexes = [];
|
|
2514
|
-
let hasIndex = true;
|
|
2515
|
-
let indexOfLatest = 0;
|
|
2516
|
-
while (hasIndex) {
|
|
2517
|
-
indexOfLatest = _lodash.default.findIndex(array, predicate, indexOfLatest + 1);
|
|
2518
|
-
if (indexOfLatest > -1) {
|
|
2519
|
-
indexes.push(indexOfLatest);
|
|
2520
|
-
} else {
|
|
2521
|
-
hasIndex = false;
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
return indexes;
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
/**
|
|
2528
|
-
// 使用範例
|
|
2529
|
-
const birthDate = '2005-01-01';
|
|
2530
|
-
console.log(isOver18(birthDate)); // 會返回 true 或 false
|
|
2531
|
-
*/
|
|
2532
|
-
isOverSpecificAge(birthDate, target = 18) {
|
|
2533
|
-
const age = (0, _momentTimezone.default)().diff((0, _momentTimezone.default)(birthDate, "YYYY-MM-DD"), "years");
|
|
2534
|
-
return age >= target;
|
|
2535
|
-
}
|
|
2536
|
-
isValidEmail(email) {
|
|
2537
|
-
// 正規表達式,用於匹配常見的電子郵件格式
|
|
2538
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2539
|
-
return emailRegex.test(email);
|
|
2540
|
-
}
|
|
2541
|
-
isValidTaiwaneseID(idNumber) {
|
|
2542
|
-
// 正規表達式,用於匹配中華民國身分證號碼的格式
|
|
2543
|
-
const idRegex = /^[A-Z][1-2]\d{8}$/;
|
|
2544
|
-
|
|
2545
|
-
// 檢查是否符合基本格式
|
|
2546
|
-
if (!idRegex.test(idNumber)) {
|
|
2547
|
-
return false;
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
// 檢查檢查碼
|
|
2551
|
-
const weight = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1];
|
|
2552
|
-
const firstChar = idNumber.charCodeAt(0) - 65; // 將英文字母轉換為數字
|
|
2553
|
-
let sum = firstChar * 10 + parseInt(idNumber.slice(1));
|
|
2554
|
-
for (let i = 0; i < weight.length; i++) {
|
|
2555
|
-
sum += parseInt(idNumber.charAt(i + 1)) * weight[i];
|
|
2556
|
-
}
|
|
2557
|
-
return sum % 10 === 0;
|
|
2558
|
-
}
|
|
2559
|
-
validatePersonalInfoInput(name, email, idNumber, phoneNumber, birthday, ageOfQualify = 12) {
|
|
2560
|
-
// 檢查姓名
|
|
2561
|
-
if (name.length < 2) {
|
|
2562
|
-
return {
|
|
2563
|
-
valid: false,
|
|
2564
|
-
message: "姓名至少要兩個字"
|
|
2565
|
-
};
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
// 檢查電子郵件
|
|
2569
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2570
|
-
if (!emailRegex.test(email)) {
|
|
2571
|
-
return {
|
|
2572
|
-
valid: false,
|
|
2573
|
-
message: "電子郵件格式不正確"
|
|
2574
|
-
};
|
|
2575
|
-
}
|
|
2576
|
-
|
|
2577
|
-
// 檢查身分證號碼 (這裡使用簡化的檢查,實際上還需要更詳細的驗證)
|
|
2578
|
-
const idRegex = /^[A-Z][1-2]\d{8}$/;
|
|
2579
|
-
if (!idRegex.test(idNumber)) {
|
|
2580
|
-
return {
|
|
2581
|
-
valid: false,
|
|
2582
|
-
message: "身分證號碼格式不正確"
|
|
2583
|
-
};
|
|
2584
|
-
}
|
|
2585
|
-
|
|
2586
|
-
// 檢查手機號碼 (這裡以台灣手機號碼為例,09開頭,共10位數字)
|
|
2587
|
-
const phoneRegex = /^09\d{8}$/;
|
|
2588
|
-
if (!phoneRegex.test(phoneNumber)) {
|
|
2589
|
-
return {
|
|
2590
|
-
valid: false,
|
|
2591
|
-
message: "手機號碼格式不正確"
|
|
2592
|
-
};
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
|
-
// 檢查生日和年齡
|
|
2596
|
-
if (this.isUndefinedNullEmpty(birthday)) return {
|
|
2597
|
-
valid: false,
|
|
2598
|
-
message: `出生日期格式不正確`
|
|
2599
|
-
};
|
|
2600
|
-
const now = (0, _momentTimezone.default)();
|
|
2601
|
-
const age = now.diff(birthday, "years");
|
|
2602
|
-
if (age < ageOfQualify) {
|
|
2603
|
-
return {
|
|
2604
|
-
valid: false,
|
|
2605
|
-
message: `年齡不得小於 ${ageOfQualify} 歲`
|
|
2606
|
-
};
|
|
2607
|
-
}
|
|
2608
|
-
|
|
2609
|
-
// 所有項目都通過檢查
|
|
2610
|
-
return {
|
|
2611
|
-
valid: true,
|
|
2612
|
-
message: "格式檢查通過"
|
|
2613
|
-
};
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
/**
|
|
2617
|
-
* // 測試範例
|
|
2618
|
-
* const startTimestamp = 1683004800000; // 2023-05-01
|
|
2619
|
-
* const endTimestamp = 1688160000000; // 2023-06-30
|
|
2620
|
-
*
|
|
2621
|
-
* console.log(formatTimestampRangeWithMoment(startTimestamp, endTimestamp));
|
|
2622
|
-
* // 輸出:23/05/01 - 06/30
|
|
2623
|
-
*
|
|
2624
|
-
* const startTimestampCrossYear = 1609459200000; // 2021-01-01
|
|
2625
|
-
* const endTimestampCrossYear = 1640995200000; // 2022-01-01
|
|
2626
|
-
*
|
|
2627
|
-
* console.log(formatTimestampRangeWithMoment(startTimestampCrossYear, endTimestampCrossYear));
|
|
2628
|
-
* // 輸出:21/01/01 - 22/01/01
|
|
2629
|
-
* */
|
|
2630
|
-
|
|
2631
|
-
getStringOfFormatTimestampRange(startTimestamp, endTimestamp) {
|
|
2632
|
-
// 使用 moment 解析 timestamp
|
|
2633
|
-
const startDate = (0, _momentTimezone.default)(startTimestamp);
|
|
2634
|
-
const endDate = (0, _momentTimezone.default)(endTimestamp);
|
|
2635
|
-
|
|
2636
|
-
// 格式化日期為 YY/MM/DD 格式
|
|
2637
|
-
const formatDate = date => date.format("YY/MM/DD");
|
|
2638
|
-
|
|
2639
|
-
// 判斷是否跨年份
|
|
2640
|
-
const startYear = startDate.year();
|
|
2641
|
-
const endYear = endDate.year();
|
|
2642
|
-
if (startYear === endYear) {
|
|
2643
|
-
// 如果沒有跨年份,顯示 YY/MM/DD - MM/DD
|
|
2644
|
-
return `${formatDate(startDate)} - ${endDate.format("MM/DD")}`;
|
|
2645
|
-
} else {
|
|
2646
|
-
// 如果跨年份,顯示 YY/MM/DD - YY/MM/DD
|
|
2647
|
-
return `${formatDate(startDate)} - ${formatDate(endDate)}`;
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
/**
|
|
2652
|
-
* // 測試範例
|
|
2653
|
-
* const startTimestamp = 1683004800000; // 2023-05-01
|
|
2654
|
-
* const endTimestamp = 1688160000000; // 2023-06-30
|
|
2655
|
-
* const weeklyMinutes = 180; // 每週上課 180 分鐘 (3 小時)
|
|
2656
|
-
*
|
|
2657
|
-
* console.log(calculateClassTimeWithMoment(startTimestamp, endTimestamp, weeklyMinutes));
|
|
2658
|
-
* // 輸出:12小時
|
|
2659
|
-
*
|
|
2660
|
-
* console.log(utiller.getStringOfCalculateClassTime(utiller.convertDateToTimestamp('2024-09-15'),utiller.convertDateToTimestamp('2024-10-15'),60))
|
|
2661
|
-
*
|
|
2662
|
-
*/
|
|
2663
|
-
getStringOfCalculateClassTime(startTimestamp, endTimestamp, weeklyMinutes) {
|
|
2664
|
-
// 使用 moment 解析 timestamp
|
|
2665
|
-
const startDate = (0, _momentTimezone.default)(startTimestamp);
|
|
2666
|
-
const endDate = (0, _momentTimezone.default)(endTimestamp);
|
|
2667
|
-
|
|
2668
|
-
// 計算時間範圍內的天數
|
|
2669
|
-
const totalDays = endDate.diff(startDate, "days") + 1; // 包含起始日
|
|
2670
|
-
const totalWeeks = Math.ceil(totalDays / 7); // 計算有幾週
|
|
2671
|
-
|
|
2672
|
-
// 計算總上課時間(分鐘)
|
|
2673
|
-
const totalMinutes = totalWeeks * weeklyMinutes;
|
|
2674
|
-
|
|
2675
|
-
// 將分鐘轉換為小時和分鐘
|
|
2676
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
2677
|
-
const minutes = totalMinutes % 60;
|
|
2678
|
-
|
|
2679
|
-
// 判斷是否需要顯示分鐘
|
|
2680
|
-
if (minutes === 0) {
|
|
2681
|
-
return `${hours}小時`;
|
|
2682
|
-
} else {
|
|
2683
|
-
return `${hours}小時${minutes}分鐘`;
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
/** // 測試範例
|
|
2688
|
-
const startTimestamp = 1683004800000; // 2023-05-01 00:00
|
|
2689
|
-
const endTimestamp = 1683040800000; // 2023-05-01 10:00
|
|
2690
|
-
const totalMinutes = getNumberOfPeriodMinute(startTimestamp, endTimestamp);
|
|
2691
|
-
console.log(totalMinutes); // 輸出:600(相當於10個小時,600分鐘)
|
|
2692
|
-
*/
|
|
2693
|
-
getNumberOfPeriodMinute(startTimestamp, endTimestamp) {
|
|
2694
|
-
// 使用 moment 解析 timestamp
|
|
2695
|
-
// 使用 moment 解析 timestamp 並只取時間的 hh:mm 部分
|
|
2696
|
-
const startTime = (0, _momentTimezone.default)(startTimestamp).format("HH:mm");
|
|
2697
|
-
const endTime = (0, _momentTimezone.default)(endTimestamp).format("HH:mm");
|
|
2698
|
-
|
|
2699
|
-
// 使用 moment 重新將 hh:mm 轉換為完整的日期對象
|
|
2700
|
-
const startDate = (0, _momentTimezone.default)(startTime, "HH:mm");
|
|
2701
|
-
const endDate = (0, _momentTimezone.default)(endTime, "HH:mm");
|
|
2702
|
-
|
|
2703
|
-
// 計算兩個時間之間的分鐘差距
|
|
2704
|
-
const durationInMinutes = _momentTimezone.default.duration(endDate.diff(startDate)).asMinutes();
|
|
2705
|
-
return durationInMinutes;
|
|
2706
|
-
}
|
|
2707
|
-
/**
|
|
2708
|
-
* const day = 1; // 週一
|
|
2709
|
-
* const startTimestamp = 1683004800000; // 2023-05-01 00:00
|
|
2710
|
-
* const endTimestamp = 1683019200000; // 2023-05-01 04:00
|
|
2711
|
-
* const formattedString = formatTimeRange(day, startTimestamp, endTimestamp);
|
|
2712
|
-
* console.log(formattedString); // 輸出:週一 00:00-04:00
|
|
2713
|
-
*/
|
|
2714
|
-
getStringOfWeekTime(day, startTimestamp, endTimestamp) {
|
|
2715
|
-
// 檢查 day 是否在 1 到 7 之間
|
|
2716
|
-
const daysOfWeek = {
|
|
2717
|
-
1: "週一",
|
|
2718
|
-
2: "週二",
|
|
2719
|
-
3: "週三",
|
|
2720
|
-
4: "週四",
|
|
2721
|
-
5: "週五",
|
|
2722
|
-
6: "週六",
|
|
2723
|
-
7: "週日"
|
|
2724
|
-
};
|
|
2725
|
-
if (day < 1 || day > 7) {
|
|
2726
|
-
throw new Error("day 必須在 1 到 7 之間");
|
|
2727
|
-
}
|
|
2728
|
-
|
|
2729
|
-
// 使用 moment 將 timestamp 轉換為只保留 hh:mm 的格式
|
|
2730
|
-
const startTime = (0, _momentTimezone.default)(startTimestamp).format("HH:mm");
|
|
2731
|
-
const endTime = (0, _momentTimezone.default)(endTimestamp).format("HH:mm");
|
|
2732
|
-
|
|
2733
|
-
// 組合結果並返回
|
|
2734
|
-
return `${daysOfWeek[day]} ${startTime}-${endTime}`;
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
/** 這個函式使用了正則表達式 \d+ 來匹配字串中的數字,並將其轉換為 number 型態。如果字串中沒有找到數字,則會回傳 null。
|
|
2738
|
-
* console.log(extractNumber('NTD 320')); // 輸出: 320
|
|
2739
|
-
* */
|
|
2740
|
-
extractNumber(str) {
|
|
2741
|
-
// 使用正則表達式提取數字部分
|
|
2742
|
-
if (this.isUndefinedNullEmpty(str)) return -1;
|
|
2743
|
-
const match = str.match(/\d+/);
|
|
2744
|
-
|
|
2745
|
-
// 如果找到數字,轉換為數字型態並回傳
|
|
2746
|
-
return match ? Number(match[0]) : -1;
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
/** puppeteer 的 fetch function
|
|
2750
|
-
* 使用這個function的朋友必須安裝puppeteer:v23.6
|
|
2751
|
-
* dom => <p id='_id' class='_class'>innerText /p>
|
|
2752
|
-
* dom的物件型態為 CdpElementHandler
|
|
2753
|
-
* * */
|
|
2754
|
-
async fetchElementAttribute(dom, attr = "innerText", defaultValue = "") {
|
|
2755
|
-
return await dom.evaluate(el => el[attr]);
|
|
2756
|
-
}
|
|
2757
|
-
|
|
2758
|
-
/** puppeteer 的 fetch function
|
|
2759
|
-
* 使用這個function的朋友必須安裝puppeteer:v23.6
|
|
2760
|
-
*
|
|
2761
|
-
* dom的物件型態為 CdpElementHandler
|
|
2762
|
-
* */
|
|
2763
|
-
async fetchElementAttributes(dom, stringOfTrait, defaultValue = "", ...attributes) {
|
|
2764
|
-
const element = await dom.$(stringOfTrait);
|
|
2765
|
-
if (!this.isUndefinedNullEmpty(element)) {
|
|
2766
|
-
try {
|
|
2767
|
-
return await element.evaluate((el, attributes) => {
|
|
2768
|
-
if (attributes.length === 1) return el[attributes.shift()];
|
|
2769
|
-
return {
|
|
2770
|
-
...attributes.map(attr => el[attr])
|
|
2771
|
-
}; //或者 el.getAttribute('src') 更精確!
|
|
2772
|
-
}, attributes);
|
|
2773
|
-
} catch (error) {
|
|
2774
|
-
this.appendError(`1581532 ${stringOfTrait} fetch ${JSON.stringify(attributes)} fail, element is not found`);
|
|
2775
|
-
return defaultValue;
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
return defaultValue;
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
/** puppeteer 的 write dom function
|
|
2782
|
-
* 使用這個function的朋友必須安裝puppeteer:v23.6
|
|
2783
|
-
* attribute = {name:value}; // {value:'100000'}, {src:'http://123.com'}
|
|
2784
|
-
* dom的物件型態為 CdpElementHandler
|
|
2785
|
-
* */
|
|
2786
|
-
async writeElementAttributes(dom, stringOfTrait, ...attributes) {
|
|
2787
|
-
const element = await dom.$(stringOfTrait);
|
|
2788
|
-
if (!this.isUndefinedNullEmpty(element)) {
|
|
2789
|
-
await element.evaluate((element, attributes) => {
|
|
2790
|
-
attributes.map(attr => {
|
|
2791
|
-
const entries = Object.entries(attr);
|
|
2792
|
-
const key = entries[0][0]; // 获取键 'name'
|
|
2793
|
-
const value = entries[0][1]; // 获取值 'value'
|
|
2794
|
-
element[key] = value;
|
|
2795
|
-
});
|
|
2796
|
-
}, attributes);
|
|
2797
|
-
} else this.appendError(`1231232 ${stringOfTrait} fetch ${JSON.stringify(attributes)} fail, element is not found`);
|
|
2798
|
-
}
|
|
2799
|
-
/**
|
|
2800
|
-
* 優化版本:根據元素類型選擇最高效的去重方式
|
|
2801
|
-
* @param {Array} array - 要去重的陣列
|
|
2802
|
-
* @param {string} [key] - (可選) 如果是物件陣列,指定用於判斷唯一的屬性鍵名
|
|
2803
|
-
* @returns {Array} - 去重後的數組
|
|
2804
|
-
*
|
|
2805
|
-
* // 使用範例
|
|
2806
|
-
* const strings = ['eee', 'aaa', 'bbb', 'ccc', 'bbb', 'ddd', 'eee'];
|
|
2807
|
-
* const objects = [
|
|
2808
|
-
* { aa: 1, bb: 2 },
|
|
2809
|
-
* { cc: 1, dd: 2 },
|
|
2810
|
-
* { aa: 1, bb: 2 },
|
|
2811
|
-
* { ee: 4, ff: 5 },
|
|
2812
|
-
* { cc: 1, dd: 2 },
|
|
2813
|
-
* ];
|
|
2814
|
-
*
|
|
2815
|
-
* console.log(uniqueArray(strings)); // ['eee', 'aaa', 'bbb', 'ccc', 'ddd']
|
|
2816
|
-
* console.log(uniqueArray(objects)); // [{'aa': 1, 'bb': 2}, {'cc': 1, 'dd': 2}, {'ee': 4, 'ff': 5}]
|
|
2817
|
-
*/
|
|
2818
|
-
getSliceArrayOfUnique(array) {
|
|
2819
|
-
if (!Array.isArray(array) || array.length === 0) {
|
|
2820
|
-
return [];
|
|
2821
|
-
}
|
|
2822
|
-
const firstElement = array[0];
|
|
2823
|
-
|
|
2824
|
-
// 1. 處理物件陣列,且提供了 key
|
|
2825
|
-
if (_lodash.default.isObject(firstElement) && key) {
|
|
2826
|
-
// 使用 Map 根據 key 去重,效率 O(N)
|
|
2827
|
-
const uniqueMap = new Map(array.map(item => [item[key], item]));
|
|
2828
|
-
return Array.from(uniqueMap.values());
|
|
2829
|
-
}
|
|
2830
|
-
// 2. 處理物件陣列,但未提供 key (或 key 無效)
|
|
2831
|
-
else if (_lodash.default.isObject(firstElement)) {
|
|
2832
|
-
// 回退到 lodash 的深度比較,效率較低 O(N^2)
|
|
2833
|
-
console.warn("getSliceArrayOfUniqueOptimized: No key provided for object array, using potentially slow deep comparison.");
|
|
2834
|
-
return _lodash.default.uniqWith(array, _lodash.default.isEqual);
|
|
2835
|
-
}
|
|
2836
|
-
// 3. 處理基本型別陣列 (string, number, boolean, null, undefined, symbol)
|
|
2837
|
-
else {
|
|
2838
|
-
// 使用 Set 去重,效率 O(N)
|
|
2839
|
-
return Array.from(new Set(array));
|
|
2840
|
-
}
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
/**
|
|
2844
|
-
* Extract unique values of a specific key from an array of objects.
|
|
2845
|
-
* array = [ { valueOfType: 1 }, { valueOfType: 7, valueOfSubType: 6 }, { valueOfType: 1 } ];
|
|
2846
|
-
console.log(getUniqueValuesBy(array, 'valueOfType')); //[1, 7]
|
|
2847
|
-
*
|
|
2848
|
-
* @param {Array<Object>} array - The array of objects to process.
|
|
2849
|
-
* @param {string} key - The key to extract values from. Default is 'valueOfType'.
|
|
2850
|
-
* @returns {Array<any>} A deduplicated array of the extracted values.
|
|
2851
|
-
*/
|
|
2852
|
-
getUniqueValuesBy(array, key = "valueOfType") {
|
|
2853
|
-
return _lodash.default.uniq(array.map(item => item[key]));
|
|
2854
|
-
}
|
|
2855
|
-
|
|
2856
|
-
/**
|
|
2857
|
-
* ({key: 'color', label: '顏色', options: [ { value: 0, label: '紅' }, { value: 1, label: '白' }, { value: 2, label: '黑'}]},
|
|
2858
|
-
* {key: 'size', label: '尺寸', options: [ { value: 0, label: 'S號' }, { value: 1, label: 'M號' }, { value: 2, label: 'L號' }]})
|
|
2859
|
-
*
|
|
2860
|
-
* [
|
|
2861
|
-
* { trait: {color: 0, size: 0}, id: 'color_0_size_0', content: '紅|S號' },
|
|
2862
|
-
* { trait: {color: 0, size: 1}, id: 'color_0_size_1', content: '紅|M號' },
|
|
2863
|
-
* { trait: {color: 0, size: 2}, id: 'color_0_size_2', content: '紅|L號' },
|
|
2864
|
-
* { trait: {color: 1, size: 0}, id: 'color_1_size_0', content: '白|S號' },
|
|
2865
|
-
* { trait: {color: 1, size: 1}, id: 'color_1_size_1', content: '白|M號' },
|
|
2866
|
-
* { trait: {color: 1, size: 2}, id: 'color_1_size_2', content: '白|L號' },
|
|
2867
|
-
* ]
|
|
2868
|
-
*
|
|
2869
|
-
/**
|
|
2870
|
-
* 生成所有組合並依照 value 遞增排序,並回傳指定格式
|
|
2871
|
-
* @param {Array} attributes - 屬性陣列
|
|
2872
|
-
* @returns {Array} - 格式化組合
|
|
2873
|
-
*/
|
|
2874
|
-
generateCombinations(...attributes) {
|
|
2875
|
-
const keys = attributes.map(attr => attr.key); // 屬性順序
|
|
2876
|
-
const labelMap = _lodash.default.keyBy(attributes, "key"); // 用於 content 查 label
|
|
2877
|
-
|
|
2878
|
-
// 把每個屬性的 options 提取成格式化陣列
|
|
2879
|
-
const optionArrays = attributes.map(attr => attr.options.map(option => ({
|
|
2880
|
-
key: attr.key,
|
|
2881
|
-
value: option.value,
|
|
2882
|
-
label: option.label
|
|
2883
|
-
})));
|
|
2884
|
-
|
|
2885
|
-
// 計算笛卡兒積
|
|
2886
|
-
const cartesianProduct = _lodash.default.reduce(optionArrays, (acc, curr) => _lodash.default.flatMap(acc, a => curr.map(b => [...a, b])), [[]]);
|
|
2887
|
-
|
|
2888
|
-
// 格式化每一筆組合
|
|
2889
|
-
const results = cartesianProduct.map(combination => {
|
|
2890
|
-
const trait = {};
|
|
2891
|
-
const idParts = [];
|
|
2892
|
-
const contentParts = [];
|
|
2893
|
-
for (const {
|
|
2894
|
-
key,
|
|
2895
|
-
value,
|
|
2896
|
-
label
|
|
2897
|
-
} of combination) {
|
|
2898
|
-
trait[key] = value;
|
|
2899
|
-
idParts.push(`${key}_${value}`);
|
|
2900
|
-
contentParts.push(`${label}`);
|
|
2901
|
-
}
|
|
2902
|
-
return {
|
|
2903
|
-
trait,
|
|
2904
|
-
id: idParts.join("_"),
|
|
2905
|
-
content: contentParts.join("|")
|
|
2906
|
-
};
|
|
2907
|
-
});
|
|
2908
|
-
|
|
2909
|
-
// 排序:依照屬性順序的 value 遞增(右邊 key 變化最快)
|
|
2910
|
-
return _lodash.default.sortBy(results, item => keys.map(key => item.trait[key]));
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
|
-
/**
|
|
2914
|
-
* 從路徑字串中擷取靜態片段(忽略以指定字元開頭的參數)
|
|
2915
|
-
* @param {string} path - 輸入的路徑字串
|
|
2916
|
-
* @param {string[]} rules - 要忽略的前綴符號規則,預設為 [':']
|
|
2917
|
-
* @returns {string[]} - 篩選後的靜態段落,例如 ['dionysus', 'variants']
|
|
2918
|
-
*
|
|
2919
|
-
* const samples = [
|
|
2920
|
-
* '/dionysus/:pid/variants',
|
|
2921
|
-
* './dionysus/*pid/variants/',
|
|
2922
|
-
* '/shop/@category/:id'
|
|
2923
|
-
* ];
|
|
2924
|
-
* // 預設只忽略 ':'
|
|
2925
|
-
*
|
|
2926
|
-
* console.log(extractStaticSegments(samples[0])); // ['dionysus', 'variants']
|
|
2927
|
-
* // 忽略 ':' 與 '*'
|
|
2928
|
-
* console.log(extractStaticSegments(samples[1], [':', '*'])); // ['dionysus', 'variants']
|
|
2929
|
-
* // 忽略 ':' 與 '@'
|
|
2930
|
-
* console.log(extractStaticSegments(samples[2], [':', '@'])); // ['shop']
|
|
2931
|
-
*
|
|
2932
|
-
*/
|
|
2933
|
-
extractStaticSegments(path, rules = [":"]) {
|
|
2934
|
-
return path.trim().replace(/^\.?\/*|\/*$/g, "") // 移除開頭 './' 或 '/',結尾 '/'
|
|
2935
|
-
.split("/").filter(segment => segment && !rules.some(rule => segment.startsWith(rule)));
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
|
-
/**
|
|
2939
|
-
* const array = [{ a: 1, b: 2, c: 3 }, { a: 1, b: 2, d: 4 }];
|
|
2940
|
-
* mutateRemoveKeys(array, ['b', 'c']);
|
|
2941
|
-
* console.log(array); // ➜ [ { a: 1 }, { a: 1, d: 4 } ]
|
|
2942
|
-
*
|
|
2943
|
-
* 移除指定 keys,並原地改動原始陣列
|
|
2944
|
-
* @param {Array<Object>} array - 要修改的原始 array
|
|
2945
|
-
* @param {Array<string>} keysToRemove - 要刪除的 key 清單
|
|
2946
|
-
*/
|
|
2947
|
-
mutateRemoveKeys(array, keysToRemove) {
|
|
2948
|
-
_lodash.default.forEach(array, (obj, index) => {
|
|
2949
|
-
const filtered = Object.fromEntries(Object.entries(obj).filter(([key]) => !keysToRemove.includes(key)));
|
|
2950
|
-
// 原地替換每個 object 的 key
|
|
2951
|
-
Object.keys(obj).forEach(k => delete obj[k]);
|
|
2952
|
-
Object.assign(obj, filtered);
|
|
2953
|
-
});
|
|
2954
|
-
}
|
|
2955
|
-
|
|
2956
|
-
/**
|
|
2957
|
-
* const array = [{ a: 1, b: 2, c: 3 }, { a: 1, b: 2, d: 4 }];
|
|
2958
|
-
* const newArray = removeKeysFromArrayObjects(array, ['b', 'c']);
|
|
2959
|
-
*
|
|
2960
|
-
* console.log(newArray); // ➜ [ { a: 1 }, { a: 1, d: 4 } ]
|
|
2961
|
-
* console.log(array); // ➜ 原始 array 不變
|
|
2962
|
-
*
|
|
2963
|
-
* 回傳一個新的 array,移除每個物件中的指定 keys
|
|
2964
|
-
* @param {Array<Object>} array - 原始資料陣列
|
|
2965
|
-
* @param {Array<string>} keysToRemove - 要移除的 key 名稱陣列
|
|
2966
|
-
* @returns {Array<Object>} - 新的 array(不改變原本的 array)
|
|
2967
|
-
*/
|
|
2968
|
-
removeKeysFromArrayObjects(array, keysToRemove) {
|
|
2969
|
-
return _lodash.default.map(array, obj => Object.fromEntries(Object.entries(obj).filter(([key]) => !keysToRemove.includes(key))));
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
/**
|
|
2973
|
-
* 將過長的文字裁切為「前段......後段」格式
|
|
2974
|
-
* @param {string} originalText - 原始文字內容
|
|
2975
|
-
* @param {number} maxLength - 最終輸出不得超過的總字數(含省略號)
|
|
2976
|
-
* @returns {string} - 處理後的顯示文字
|
|
2977
|
-
*/
|
|
2978
|
-
formatTextWithEllipsis(originalText, maxLength) {
|
|
2979
|
-
const ellipsis = "......";
|
|
2980
|
-
const ellipsisLength = ellipsis.length;
|
|
2981
|
-
|
|
2982
|
-
// 若文字本身就短,無需裁切
|
|
2983
|
-
if (_lodash.default.size(originalText) <= maxLength) return originalText;
|
|
2984
|
-
|
|
2985
|
-
// 若 maxLength 小於 ellipsis 自身長度,回傳空字串或提示錯誤
|
|
2986
|
-
if (maxLength <= ellipsisLength) return "";
|
|
2987
|
-
|
|
2988
|
-
// 可用來切出前後字串的總長度
|
|
2989
|
-
const remainingLength = maxLength - ellipsisLength;
|
|
2990
|
-
|
|
2991
|
-
// 前後平均切一半(如果是奇數則前段較短)
|
|
2992
|
-
const frontLength = Math.floor(remainingLength / 2);
|
|
2993
|
-
const backLength = remainingLength - frontLength;
|
|
2994
|
-
const front = _lodash.default.truncate(originalText, {
|
|
2995
|
-
length: frontLength,
|
|
2996
|
-
omission: ""
|
|
2997
|
-
});
|
|
2998
|
-
const back = _lodash.default.takeRight(originalText, backLength).join("");
|
|
2999
|
-
return `${front}${ellipsis}${back}`;
|
|
3000
|
-
}
|
|
3001
|
-
|
|
3002
|
-
/**
|
|
3003
|
-
* const obj = {
|
|
3004
|
-
* a: { idOfBooze: 1, checked: true },
|
|
3005
|
-
* b: { idOfBooze: 2, checked: false },
|
|
3006
|
-
* c: { idOfBooze: 3 }, // 無 checked
|
|
3007
|
-
* d: { idOfBooze: 4, checked: true }
|
|
3008
|
-
* };
|
|
3009
|
-
*
|
|
3010
|
-
* getObjectBy(obj) ==> { b: { idOfBooze: 2, checked: false }, c: { idOfBooze: 3 } }
|
|
3011
|
-
*
|
|
3012
|
-
* 從物件中依條件過濾出符合條件的 key-value pair
|
|
3013
|
-
* @param {Object} obj - 原始物件
|
|
3014
|
-
* @param {Function} predict - 過濾條件函式,預設為 each.used === true
|
|
3015
|
-
* @returns {Object} - 符合條件的新物件
|
|
3016
|
-
*/
|
|
3017
|
-
getObjectBy(obj, predict = attr => attr.checked !== true) {
|
|
3018
|
-
return _lodash.default.fromPairs(_lodash.default.toPairs(obj).filter(([_, value]) => predict(value)));
|
|
3019
|
-
}
|
|
3020
|
-
|
|
3021
|
-
/**
|
|
3022
|
-
*
|
|
3023
|
-
const array = [
|
|
3024
|
-
{ serial: 'A023' },
|
|
3025
|
-
{ serial: 'Z001' },
|
|
3026
|
-
{ serial: 'C002' },
|
|
3027
|
-
{ serial: 'G123' },
|
|
3028
|
-
{ serial: 'A001' },
|
|
3029
|
-
{ serial: 'A999' }
|
|
3030
|
-
];
|
|
3031
|
-
mutateBy(array, (item) => {
|
|
3032
|
-
const serial = item.serial;
|
|
3033
|
-
const match = serial.match(/^([A-Z]+)(\d+)$/i);
|
|
3034
|
-
const [letter, number] = match ? [match[1], parseInt(match[2], 10)] : [serial, 0];
|
|
3035
|
-
return [letter, number]; // 多層排序:先字母,再數字
|
|
3036
|
-
});
|
|
3037
|
-
*
|
|
3038
|
-
* [ { serial: 'A001' }, { serial: 'A023' }, { serial: 'A999' }, { serial: 'C002' }, { serial: 'G123' }, { serial: 'Z001' } ]
|
|
3039
|
-
*
|
|
3040
|
-
*
|
|
3041
|
-
* 通用的排序變異工具:依照 predict 提供的排序 key 對 array 進行原地排序(mutated)
|
|
3042
|
-
*
|
|
3043
|
-
* @param {Array} array - 要排序的陣列(會就地變異)
|
|
3044
|
-
* @param {Function} predict - 回傳排序 key(可以是陣列以支援多層排序)
|
|
3045
|
-
*/
|
|
3046
|
-
mutateBy(array, predict = item => item) {
|
|
3047
|
-
const sorted = _lodash.default.sortBy(array, predict);
|
|
3048
|
-
array.splice(0, array.length, ...sorted);
|
|
3049
|
-
}
|
|
3050
|
-
|
|
3051
|
-
/**
|
|
3052
|
-
*
|
|
3053
|
-
* const array1 = ['a', 'b', 'c', null];
|
|
3054
|
-
* const array2 = ['b', '', 'd'];
|
|
3055
|
-
* const array3 = ['c', undefined, 'e'];
|
|
3056
|
-
* const result = findUniqueStrings(array1, array2, array3);
|
|
3057
|
-
* console.log(result); // ['a', 'd', 'e']
|
|
3058
|
-
*
|
|
3059
|
-
**/
|
|
3060
|
-
findUniqueStrings(...arrays) {
|
|
3061
|
-
const allStrings = _lodash.default.flatten(arrays);
|
|
3062
|
-
const grouped = _lodash.default.countBy(allStrings);
|
|
3063
|
-
return _lodash.default.chain(grouped).pickBy(count => count === 1).keys().compact() // 移除 null、undefined、''、0、false、NaN
|
|
3064
|
-
.value();
|
|
3065
|
-
}
|
|
3066
|
-
/**
|
|
3067
|
-
* 減少不必要的{}
|
|
3068
|
-
* 例如 array.map(each => {return {key,value}})
|
|
3069
|
-
**/
|
|
3070
|
-
getObjectOfSpecifyKey(value, key) {
|
|
3071
|
-
const object = {};
|
|
3072
|
-
object[key] = value;
|
|
3073
|
-
return object;
|
|
3074
|
-
}
|
|
3075
|
-
|
|
3076
|
-
/**
|
|
3077
|
-
*
|
|
3078
|
-
* 參考第一個陣列(array1);
|
|
3079
|
-
* 回傳所有其他陣列中:
|
|
3080
|
-
* 不在第一個陣列中的字串;
|
|
3081
|
-
* 只出現一次的字串(全體中只出現一次);
|
|
3082
|
-
*
|
|
3083
|
-
* const array1 = ['apple', 'banana', 'cherry'];
|
|
3084
|
-
* const array2 = ['banana', '', 'date', null];
|
|
3085
|
-
* const array3 = ['apple', undefined, 'elderberry'];
|
|
3086
|
-
* const array4 = ['grape', '', 'honeydew', 'grape'];
|
|
3087
|
-
* const result = findUniqueNonReferenceStrings(array1, array2, array3, array4);
|
|
3088
|
-
* console.log(result); // ['date', 'elderberry', 'honeydew']
|
|
3089
|
-
*
|
|
3090
|
-
*/
|
|
3091
|
-
findUniqueNonReferenceStrings(...arrays) {
|
|
3092
|
-
if (arrays.length === 0) return [];
|
|
3093
|
-
const [reference, ...rest] = arrays;
|
|
3094
|
-
const allExceptRef = _lodash.default.flatten(rest);
|
|
3095
|
-
const counted = _lodash.default.countBy(allExceptRef);
|
|
3096
|
-
return _lodash.default.chain(counted).pickBy((count, str) => count === 1 && !reference.includes(str)).keys().compact() // 過濾掉 null, undefined, '' 等 falsy 值
|
|
3097
|
-
.value();
|
|
3098
|
-
}
|
|
3099
|
-
/**
|
|
3100
|
-
* const input = [
|
|
3101
|
-
* { value: 'xx0132', label: 'A款' },
|
|
3102
|
-
* { value: 'y1y123', label: 'B款' },
|
|
3103
|
-
* { value: 'yy0123', label: 'C款' },
|
|
3104
|
-
* { value: '', label: 'D款' },
|
|
3105
|
-
* { value: null, label: 'E款' },
|
|
3106
|
-
* { value: undefined, label: 'F款' },
|
|
3107
|
-
* ]
|
|
3108
|
-
*
|
|
3109
|
-
* const output = getArrayOfFillMissingValues(input)
|
|
3110
|
-
* console.log(output)
|
|
3111
|
-
* */
|
|
3112
|
-
getArrayOfFillMissingValues(array) {
|
|
3113
|
-
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
3114
|
-
const generateRandomValue = () => _lodash.default.times(8, () => _lodash.default.sample(charset)).join("");
|
|
3115
|
-
const usedValues = new Set(array.map(item => item.value).filter(Boolean));
|
|
3116
|
-
return array.map(item => {
|
|
3117
|
-
if (_lodash.default.isEmpty(item.value)) {
|
|
3118
|
-
let newValue;
|
|
3119
|
-
do {
|
|
3120
|
-
newValue = generateRandomValue();
|
|
3121
|
-
} while (usedValues.has(newValue));
|
|
3122
|
-
usedValues.add(newValue);
|
|
3123
|
-
return {
|
|
3124
|
-
...item,
|
|
3125
|
-
value: newValue
|
|
3126
|
-
};
|
|
3127
|
-
}
|
|
3128
|
-
return item;
|
|
3129
|
-
});
|
|
3130
|
-
}
|
|
3131
|
-
|
|
3132
|
-
/**
|
|
3133
|
-
* 判斷是否為 Firestore 自動產生的 Document ID
|
|
3134
|
-
* @param {string} id - 欲檢查的字串
|
|
3135
|
-
* @returns {boolean}
|
|
3136
|
-
*
|
|
3137
|
-
* isFirestoreAutoId('Ab3dEFghiJKLmnPQrStu'); // ✅ true
|
|
3138
|
-
* isFirestoreAutoId('1234567890abcdefghij'); // ✅ true
|
|
3139
|
-
* isFirestoreAutoId('a-b-c-d-e-f-g-h-i-j'); // ❌ false(有非法字元)
|
|
3140
|
-
* isFirestoreAutoId('shortId'); // ❌ false(長度錯誤)
|
|
3141
|
-
* isFirestoreAutoId(null); // ❌ false(不是字串)
|
|
3142
|
-
*/
|
|
3143
|
-
isFirestoreAutoId(id) {
|
|
3144
|
-
return _lodash.default.isString(id) && id.length === 20 && /^[A-Za-z0-9]{20}$/.test(id);
|
|
3145
|
-
}
|
|
3146
|
-
getAutoIdOfFirestore() {
|
|
3147
|
-
return this.getRandomHashV2(20);
|
|
3148
|
-
}
|
|
3149
|
-
/** const result = convertTimeRange('2025/08/18(一)|16:00-17:00');
|
|
3150
|
-
console.log(result); // '202508181600-202508181700'*/
|
|
3151
|
-
getStringOfConvertTimeRange(input) {
|
|
3152
|
-
const [datePart, timeRange] = input.split("|");
|
|
3153
|
-
const dateStr = datePart.split("(")[0]; // 取 '2025/08/18'
|
|
3154
|
-
const [start, end] = timeRange.split("-");
|
|
3155
|
-
const formatDate = time => (0, _momentTimezone.default)(`${dateStr} ${time}`, "YYYY/MM/DD HH:mm").format("YYYYMMDDHHmm");
|
|
3156
|
-
return `${formatDate(start)}-${formatDate(end)}`;
|
|
3157
|
-
}
|
|
3158
|
-
|
|
3159
|
-
/**
|
|
3160
|
-
* console.log(getTSOfSpecificDate("2025/08/18(一)", { end: false })); // 20250818000000 (number)
|
|
3161
|
-
* console.log(getTSOfSpecificDate("2025/08/18(一)", { end: true })); // 20250818235959 (number)
|
|
3162
|
-
*/
|
|
3163
|
-
getTSOfSpecificDate(dateStr, {
|
|
3164
|
-
end = false
|
|
3165
|
-
} = {}) {
|
|
3166
|
-
return Number((0, _lodash.default)(dateStr).thru(str => (0, _momentTimezone.default)(str.split("(")[0], "YYYY/MM/DD")).thru(m => end ? m.endOf("day") : m.startOf("day")).value() // 取出 moment 物件
|
|
3167
|
-
.format("YYYYMMDDHHmmss"));
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
|
-
/**
|
|
3171
|
-
* 檢查是否為 HTTPS 網址
|
|
3172
|
-
* @param {string} url
|
|
3173
|
-
* @returns {boolean}
|
|
3174
|
-
*/
|
|
3175
|
-
isHttpsURL(url) {
|
|
3176
|
-
if (!_lodash.default.isString(url)) return false;
|
|
3177
|
-
try {
|
|
3178
|
-
const decoded = decodeURIComponent(url.trim());
|
|
3179
|
-
const parsed = new URL(decoded);
|
|
3180
|
-
return parsed.protocol === "https:";
|
|
3181
|
-
} catch (e) {
|
|
3182
|
-
return false;
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
/**
|
|
3186
|
-
* 🧩 產生合法變數命名的唯一亂碼代碼對照表(支援自訂長度)
|
|
3187
|
-
*
|
|
3188
|
-
* 將輸入的字串陣列轉換成:
|
|
3189
|
-
* 1️⃣ 合法變數命名 key(以 _.camelCase() 處理)
|
|
3190
|
-
* 2️⃣ 對應唯一亂碼代碼(預設長度 3,第一字母必須為英文字母)
|
|
3191
|
-
* 3️⃣ 若 key 重複,拋出錯誤並指出是哪個 key 重複
|
|
3192
|
-
*
|
|
3193
|
-
* @param {string[]} array - 要轉換的字串陣列
|
|
3194
|
-
* @param {number} [length=3] - 代碼長度(預設為 3,最小為 2)
|
|
3195
|
-
* @returns {Object} 回傳一個 JSON 物件,例如:
|
|
3196
|
-
* { mainDiv: 'f2x', mainBanner: 'k9A' }
|
|
3197
|
-
*
|
|
3198
|
-
* 📘 範例:
|
|
3199
|
-
* ```js
|
|
3200
|
-
* const arr = ["MainDiv", "MainPromotedBannerSwiperSlide", "MainPromotedBannerSwiperList"];
|
|
3201
|
-
*
|
|
3202
|
-
* console.log(generateUniqueCodeMap(arr)); // 預設長度3
|
|
3203
|
-
* // => { mainDiv: 'a9F', mainPromotedBannerSwiperSlide: 'm2q', mainPromotedBannerSwiperList: 'z5K' }
|
|
3204
|
-
*
|
|
3205
|
-
* console.log(generateUniqueCodeMap(arr, 4)); // 改為4字元
|
|
3206
|
-
* // => { mainDiv: 'a9Fz', mainPromotedBannerSwiperSlide: 'm2qR', mainPromotedBannerSwiperList: 'z5K2' }
|
|
3207
|
-
* ```
|
|
3208
|
-
*
|
|
3209
|
-
* // === 🧪 測試範例 ===
|
|
3210
|
-
* const arr = [
|
|
3211
|
-
* "MainDiv",
|
|
3212
|
-
* "MainPromotedBannerSwiperSlide",
|
|
3213
|
-
* "MainPromotedBannerSwiperList",
|
|
3214
|
-
* ];
|
|
3215
|
-
*
|
|
3216
|
-
* console.log("3字元預設:", generateUniqueCodeMap(arr));
|
|
3217
|
-
* console.log("4字元代碼:", generateUniqueCodeMap(arr, 4));
|
|
3218
|
-
*
|
|
3219
|
-
*/
|
|
3220
|
-
generateUniqueCodeMap(array, length = 3) {
|
|
3221
|
-
if (length < 2) {
|
|
3222
|
-
throw new Error("代碼長度最少必須為 2。");
|
|
3223
|
-
}
|
|
3224
|
-
const usedCodes = new Set();
|
|
3225
|
-
const usedKeys = new Set();
|
|
3226
|
-
|
|
3227
|
-
/**
|
|
3228
|
-
* 產生合法變數代碼(指定長度亂碼)
|
|
3229
|
-
* 第1字母:a-zA-Z
|
|
3230
|
-
* 其餘字元:a-zA-Z0-9
|
|
3231
|
-
*/
|
|
3232
|
-
const generateRandomCode = () => {
|
|
3233
|
-
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
3234
|
-
const chars = letters + "0123456789";
|
|
3235
|
-
let code;
|
|
3236
|
-
do {
|
|
3237
|
-
// 第一個字母必須是英文
|
|
3238
|
-
code = letters[Math.floor(Math.random() * letters.length)];
|
|
3239
|
-
// 其餘字元隨機取
|
|
3240
|
-
for (let i = 1; i < length; i++) {
|
|
3241
|
-
code += chars[Math.floor(Math.random() * chars.length)];
|
|
3242
|
-
}
|
|
3243
|
-
} while (usedCodes.has(code));
|
|
3244
|
-
usedCodes.add(code);
|
|
3245
|
-
return code;
|
|
3246
|
-
};
|
|
3247
|
-
return _lodash.default.transform(array, (result, key) => {
|
|
3248
|
-
if (usedKeys.has(key)) {
|
|
3249
|
-
throw new Error(`23125453 Duplicate key detected: "${key}"`);
|
|
3250
|
-
}
|
|
3251
|
-
usedKeys.add(key);
|
|
3252
|
-
result[key] = generateRandomCode();
|
|
3253
|
-
}, {});
|
|
3254
|
-
}
|
|
3255
|
-
/**
|
|
3256
|
-
* 根據百分比計算價格變化後的金額(折扣後的總金額)。
|
|
3257
|
-
*
|
|
3258
|
-
* @param {number} price - 原始價格。
|
|
3259
|
-
* @param {number} percentage - 百分比(例如 10 表示 10%)。
|
|
3260
|
-
* @param {boolean} [discount=false] - 是否為折扣模式。
|
|
3261
|
-
* - true:表示減價(例如 10% 折扣 → 價格 * (1 - 0.1))
|
|
3262
|
-
* - false:表示加價(例如 10% 加成 → 價格 * (1 + 0.1))
|
|
3263
|
-
* @returns {number} - 計算後的金額,並取「向上取整」結果。
|
|
3264
|
-
*
|
|
3265
|
-
* 範例:
|
|
3266
|
-
* getPriceOfPercentageBehavior(100, 10, true) → 90
|
|
3267
|
-
* getPriceOfPercentageBehavior(100, 10, false) → 110
|
|
3268
|
-
*/
|
|
3269
|
-
getPriceOfPercentageBehavior(price, percentage, discount = false) {
|
|
3270
|
-
// 將百分比(例如 10)轉為小數(0.1)
|
|
3271
|
-
const decimal = this.toPercentageDecimal(percentage);
|
|
3272
|
-
|
|
3273
|
-
// 根據是否為折扣,決定乘上 (1 - 0.1) 或 (1 + 0.1)
|
|
3274
|
-
// 並呼叫 getNumberOfMultiplyCeil 進行乘法後向上取整
|
|
3275
|
-
return this.getNumberOfMultiplyCeil(price, discount ? 1 - decimal : 1 + decimal);
|
|
3276
|
-
}
|
|
3277
|
-
getFeeOfDiscount(origin, percentage) {
|
|
3278
|
-
return Math.round(_lodash.default.multiply(origin, this.toPercentageDecimal(percentage)));
|
|
3279
|
-
}
|
|
3280
|
-
|
|
3281
|
-
/**
|
|
3282
|
-
* 📦 mergeArrayByKey(array)
|
|
3283
|
-
* ---------------------------------------
|
|
3284
|
-
* 將陣列中相同 key 的物件進行「巢狀合併」,並直接 mutate 原陣列內容。
|
|
3285
|
-
*
|
|
3286
|
-
* ✅ 特點:
|
|
3287
|
-
* - 自動合併相同 key 的物件內容(深層合併)
|
|
3288
|
-
* - 不建立新陣列,會直接修改傳入的 array
|
|
3289
|
-
* - 適用於需要整併設定、狀態、表單資料等情境
|
|
3290
|
-
*
|
|
3291
|
-
* 🧩 範例:
|
|
3292
|
-
* ```js
|
|
3293
|
-
* const array = [
|
|
3294
|
-
* { a: { b: { c: 2 } } },
|
|
3295
|
-
* { b: { d: { g: 1 } }, a: { b: { y: 1 }, h: { e: 1 } } }
|
|
3296
|
-
* ];
|
|
3297
|
-
*
|
|
3298
|
-
* ArrayHelper.mergeArrayByKey(array);
|
|
3299
|
-
*
|
|
3300
|
-
* console.log(array);
|
|
3301
|
-
* // 👉 [ { a: { b: { c: 2, y: 1 }, h: { e: 1 } } }, { b: { d: { g: 1 } } } ]
|
|
3302
|
-
* ```
|
|
3303
|
-
*
|
|
3304
|
-
* @param {Array<Object>} array - 需被合併的物件陣列
|
|
3305
|
-
* @returns {Array<Object>} - 回傳同一個(已被 mutate 的)陣列
|
|
3306
|
-
*/
|
|
3307
|
-
mergeArrayByKey(array) {
|
|
3308
|
-
if (!Array.isArray(array)) return array;
|
|
3309
|
-
|
|
3310
|
-
// 收集所有 key 的合併結果
|
|
3311
|
-
const resultMap = {};
|
|
3312
|
-
for (const obj of array) {
|
|
3313
|
-
if (!_lodash.default.isPlainObject(obj)) continue;
|
|
3314
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
3315
|
-
if (!resultMap[key]) {
|
|
3316
|
-
resultMap[key] = _lodash.default.cloneDeep(value);
|
|
3317
|
-
} else {
|
|
3318
|
-
_lodash.default.merge(resultMap[key], value);
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
}
|
|
3322
|
-
|
|
3323
|
-
// 清空原陣列(mutate)
|
|
3324
|
-
array.length = 0;
|
|
3325
|
-
|
|
3326
|
-
// 重建結構
|
|
3327
|
-
Object.entries(resultMap).forEach(([key, value]) => {
|
|
3328
|
-
array.push({
|
|
3329
|
-
[key]: value
|
|
3330
|
-
});
|
|
3331
|
-
});
|
|
3332
|
-
return array;
|
|
3333
|
-
}
|
|
3334
|
-
|
|
3335
|
-
/**
|
|
3336
|
-
* 將時間字串解析為物件,回傳 {startDate, startTime, endDate, endTime}
|
|
3337
|
-
*
|
|
3338
|
-
* 支援格式:
|
|
3339
|
-
* 1. '2025/11/10 (一)|13:00-15:00'
|
|
3340
|
-
* => { startDate:'2025/11/10', startTime:'13:00', endDate:'2025/11/10', endTime:'15:00' }
|
|
3341
|
-
*
|
|
3342
|
-
* 2. '2025/11/10 (一) 13:00 - 2025/11/12 (三) 15:00'
|
|
3343
|
-
* => { startDate:'2025/11/10', startTime:'13:00', endDate:'2025/11/12', endTime:'15:00' }
|
|
3344
|
-
*
|
|
3345
|
-
* @param {string} input - 欲解析的時間字串
|
|
3346
|
-
* @returns {{startDate: string, startTime: string, endDate: string, endTime: string}} 解析後的時間物件
|
|
3347
|
-
*
|
|
3348
|
-
* @example
|
|
3349
|
-
* getObjectOfStartEndDateTime('2025/11/10 (一)|13:00-15:00');
|
|
3350
|
-
* // => { startDate:'2025/11/10', startTime:'13:00', endDate:'2025/11/10', endTime:'15:00' }
|
|
3351
|
-
*
|
|
3352
|
-
* @example
|
|
3353
|
-
* getObjectOfStartEndDateTime('2025/11/10 (一) 13:00 - 2025/11/12 (三) 15:00');
|
|
3354
|
-
* // => { startDate:'2025/11/10', startTime:'13:00', endDate:'2025/11/12', endTime:'15:00' }
|
|
3355
|
-
*/
|
|
3356
|
-
getObjectOfStartEndDateTime(input) {
|
|
3357
|
-
if (!input || typeof input !== "string") {
|
|
3358
|
-
return {
|
|
3359
|
-
startDate: "",
|
|
3360
|
-
startTime: "",
|
|
3361
|
-
endDate: "",
|
|
3362
|
-
endTime: ""
|
|
3363
|
-
};
|
|
3364
|
-
}
|
|
3365
|
-
|
|
3366
|
-
// 將全形符號與多餘空白轉換為標準形式
|
|
3367
|
-
const cleaned = input.replace(/|/g, " ").replace(/-/g, "-") // 全形破折號轉半形
|
|
3368
|
-
.replace(/\s+/g, " ").trim();
|
|
3369
|
-
|
|
3370
|
-
// 跨日期格式 (例:2025/11/10 13:00 - 2025/11/12 15:00)
|
|
3371
|
-
const crossDatePattern = /(\d{4}\/\d{1,2}\/\d{1,2})(?:\s*\([^)]*\))?\s*(\d{2}:\d{2})\s*-\s*(\d{4}\/\d{1,2}\/\d{1,2})(?:\s*\([^)]*\))?\s*(\d{2}:\d{2})/;
|
|
3372
|
-
|
|
3373
|
-
// 同日期格式 (例:2025/11/10 13:00 - 15:00)
|
|
3374
|
-
const sameDatePattern = /(\d{4}\/\d{1,2}\/\d{1,2})(?:\s*\([^)]*\))?\s*(\d{2}:\d{2})\s*-\s*(\d{2}:\d{2})/;
|
|
3375
|
-
let startDate, startTime, endDate, endTime;
|
|
3376
|
-
if (crossDatePattern.test(cleaned)) {
|
|
3377
|
-
[, startDate, startTime, endDate, endTime] = cleaned.match(crossDatePattern);
|
|
3378
|
-
} else if (sameDatePattern.test(cleaned)) {
|
|
3379
|
-
[, startDate, startTime, endTime] = cleaned.match(sameDatePattern);
|
|
3380
|
-
endDate = startDate;
|
|
3381
|
-
} else {
|
|
3382
|
-
return {
|
|
3383
|
-
startDate: "",
|
|
3384
|
-
startTime: "",
|
|
3385
|
-
endDate: "",
|
|
3386
|
-
endTime: ""
|
|
3387
|
-
};
|
|
3388
|
-
}
|
|
3389
|
-
|
|
3390
|
-
// 將日期時間轉為可比較的 Date 物件
|
|
3391
|
-
const start = new Date(`${startDate} ${startTime}`);
|
|
3392
|
-
const end = new Date(`${endDate} ${endTime}`);
|
|
3393
|
-
if (end < start) {
|
|
3394
|
-
throw new Error(`End time cannot be earlier than start time: ${input}`);
|
|
3395
|
-
}
|
|
3396
|
-
return {
|
|
3397
|
-
startDate,
|
|
3398
|
-
startTime,
|
|
3399
|
-
endDate,
|
|
3400
|
-
endTime
|
|
3401
|
-
};
|
|
3402
|
-
}
|
|
3403
|
-
// 範例用法 (請確保 moment.js 已載入)
|
|
3404
|
-
/*
|
|
3405
|
-
const myEvent = {
|
|
3406
|
-
title: '吉他課 (Moment.js)',
|
|
3407
|
-
startDate: '2026/01/15', // 使用新的 YYYY/MM/DD 格式
|
|
3408
|
-
startTime: '10:30',
|
|
3409
|
-
endDate: '2026/01/15',
|
|
3410
|
-
endTime: '12:00',
|
|
3411
|
-
location: '新北市板橋區縣民大道一段 1 號',
|
|
3412
|
-
details: 'Moment.js 測試:請記得帶譜架。'
|
|
3413
|
-
};
|
|
3414
|
-
if (typeof moment !== 'undefined') {
|
|
3415
|
-
const links = generateAllCalendarLinks(myEvent);
|
|
3416
|
-
console.log('--- 使用 Moment.js 生成的連結 ---');
|
|
3417
|
-
console.log(links);
|
|
3418
|
-
} else {
|
|
3419
|
-
console.warn('moment.js 函式庫未載入,無法執行。');
|
|
3420
|
-
}
|
|
3421
|
-
*/
|
|
3422
|
-
|
|
3423
|
-
/** ============== 排課系統公式 開始 ============== */
|
|
3424
|
-
|
|
3425
|
-
/**
|
|
3426
|
-
* /const arrayWithDup = [
|
|
3427
|
-
* { idOfBooze: "A111", idOfVariant: "V001", period: "202508151400-202508151500" },
|
|
3428
|
-
* { idOfBooze: "B222", idOfVariant: "V002", period: "202508151600-202508151700" },
|
|
3429
|
-
* { idOfBooze: "A111", idOfVariant: "V001", period: "202508161400-202508161500" }, // 🔁 與第一筆 PK 相同
|
|
3430
|
-
* { idOfBooze: "B222", idOfVariant: "V003", period: "202508171600-202508171700" },
|
|
3431
|
-
* { idOfBooze: "A111", idOfVariant: "V004", period: "202508181400-202508181500" }
|
|
3432
|
-
*
|
|
3433
|
-
* ];
|
|
3434
|
-
* const result = getFilteredPeriods(arrayWithDup, "B222"); console.log(result);
|
|
3435
|
-
* [ "202508151400-202508151500", "202508181400-202508181500" ]
|
|
3436
|
-
* 1. 刪掉所有 idOfBooze = "B222" 的項目
|
|
3437
|
-
* 2. idOfBooze和idOfVariant為PK, 重複的只保留一組
|
|
3438
|
-
* 3. 回傳filter array,(反查出哪些課程重複會用到其他資訊)
|
|
3439
|
-
* */
|
|
3440
|
-
getFilteredHeraPeriods(arr, idOfCurrentBooze) {
|
|
3441
|
-
return _lodash.default.chain(arr)
|
|
3442
|
-
// 1️⃣ 刪掉 idOfBooze 等於目標值的項目
|
|
3443
|
-
.filter(item => item.idOfBooze !== idOfCurrentBooze)
|
|
3444
|
-
// 2️⃣ 根據 idOfBooze+idOfVariant 組成唯一鍵 僅保留一組
|
|
3445
|
-
.uniqBy(item => `${item.idOfBooze}_${item.idOfVariant}`)
|
|
3446
|
-
// 3️⃣ 只保留 period 欄位
|
|
3447
|
-
// .map('period')
|
|
3448
|
-
// 4️⃣ 過濾掉沒有 period 的項目(避免 undefined)
|
|
3449
|
-
// .filter(Boolean)
|
|
3450
|
-
// 5️⃣ 轉回陣列
|
|
3451
|
-
.value();
|
|
3452
|
-
}
|
|
3453
|
-
|
|
3454
|
-
/**
|
|
3455
|
-
* 檢查新任務與既有任務是否有時間衝突
|
|
3456
|
-
* @param {Object} newTask - 新任務物件,需包含 timeSlot 屬性 (格式: YYYY/MM/DD (週)|HH:mm-HH:mm)
|
|
3457
|
-
* @param {Array} existingTasks - 已安排的任務陣列,每個物件需包含 period 屬性 (格式: yyyymmddHHmm-yyyymmddHHmm)
|
|
3458
|
-
* @param {number} resourceCount - 可同時處理任務的人員/資源數量
|
|
3459
|
-
* @returns {Object} { conflict: boolean, items: Array }
|
|
3460
|
-
*/
|
|
3461
|
-
checkPeriodConflict(newTask, existingTasks, resourceCount = 1) {
|
|
3462
|
-
// 1️⃣ 解析 timeSlot 拆成日期與時間
|
|
3463
|
-
const [datePart, timePart] = newTask.content.split("|");
|
|
3464
|
-
const date = (0, _momentTimezone.default)(datePart.split(" ")[0], "YYYY/MM/DD");
|
|
3465
|
-
const [startTimeStr, endTimeStr] = timePart.split("-");
|
|
3466
|
-
const start = (0, _momentTimezone.default)(`${date.format("YYYY/MM/DD")} ${startTimeStr}`, "YYYY/MM/DD HH:mm");
|
|
3467
|
-
const end = (0, _momentTimezone.default)(`${date.format("YYYY/MM/DD")} ${endTimeStr}`, "YYYY/MM/DD HH:mm");
|
|
3468
|
-
|
|
3469
|
-
// 2️⃣ 篩選出有真實時間重疊的任務
|
|
3470
|
-
const conflictItems = _lodash.default.filter(existingTasks, task => {
|
|
3471
|
-
const [pStartStr, pEndStr] = task.period.split("-");
|
|
3472
|
-
const pStart = (0, _momentTimezone.default)(pStartStr, "YYYYMMDDHHmm");
|
|
3473
|
-
const pEnd = (0, _momentTimezone.default)(pEndStr, "YYYYMMDDHHmm");
|
|
3474
|
-
|
|
3475
|
-
// 判斷條件:有交集才算衝突
|
|
3476
|
-
return start.isBefore(pEnd) && end.isAfter(pStart);
|
|
3477
|
-
});
|
|
3478
|
-
|
|
3479
|
-
// 3️⃣ 根據資源數量判斷是否真的構成衝突
|
|
3480
|
-
const conflict = conflictItems.length >= resourceCount;
|
|
3481
|
-
|
|
3482
|
-
// 4️⃣ 回傳結果
|
|
3483
|
-
return {
|
|
3484
|
-
conflict,
|
|
3485
|
-
// true: 超過資源負荷
|
|
3486
|
-
items: conflictItems // 衝突的任務列表
|
|
3487
|
-
};
|
|
3488
|
-
} // testOfConflict() {
|
|
3489
|
-
// // ===== 測試資料 =====
|
|
3490
|
-
// const newTask = {
|
|
3491
|
-
// timeSlot: "2025/08/20 (三)|16:00-17:00", // 格式: YYYY/MM/DD (週)|HH:mm-HH:mm
|
|
3492
|
-
// id: "JOB001",
|
|
3493
|
-
// description: "安裝冷氣"
|
|
3494
|
-
// };
|
|
3495
|
-
//
|
|
3496
|
-
// const existingTasks = [
|
|
3497
|
-
// { id: "T001", period: "202508151400-202508151500" },
|
|
3498
|
-
// { id: "T002", period: "202508151600-202508151700" },
|
|
3499
|
-
// { id: "T003", period: "202508161400-202508161500" },
|
|
3500
|
-
// { id: "T004", period: "202508171600-202508171700" },
|
|
3501
|
-
// { id: "T005", period: "202508201630-202508201700" }, // 衝突
|
|
3502
|
-
// { id: "T006", period: "202508201645-202508201700" } // 衝突
|
|
3503
|
-
// ];
|
|
3504
|
-
//
|
|
3505
|
-
// // 測試:一人資源
|
|
3506
|
-
// console.log(this.checkPeriodConflict(newTask, existingTasks, 1));
|
|
3507
|
-
// /*
|
|
3508
|
-
// {
|
|
3509
|
-
// conflict: true,
|
|
3510
|
-
// items: [
|
|
3511
|
-
// { id: 'T005', period: '202508201630-202508201700' },
|
|
3512
|
-
// { id: 'T006', period: '202508201645-202508201700' }
|
|
3513
|
-
// ]
|
|
3514
|
-
// }
|
|
3515
|
-
// */
|
|
3516
|
-
//
|
|
3517
|
-
// // 測試:兩人資源
|
|
3518
|
-
// console.log(this.checkPeriodConflict(newTask, existingTasks, 2));
|
|
3519
|
-
// /*
|
|
3520
|
-
// {
|
|
3521
|
-
// conflict: false,
|
|
3522
|
-
// items: [
|
|
3523
|
-
// { id: 'T005', period: '202508201630-202508201700' },
|
|
3524
|
-
// { id: 'T006', period: '202508201645-202508201700' }
|
|
3525
|
-
// ]
|
|
3526
|
-
// }
|
|
3527
|
-
// */
|
|
3528
|
-
// }
|
|
3529
|
-
/** ============== 排課系統公式 結束 ============== */
|
|
3530
|
-
}
|
|
3531
|
-
var _default = exports.default = Utiller;
|
|
1
|
+
"use strict";var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _defineProperty2=_interopRequireDefault(require("@babel/runtime/helpers/defineProperty")),_lodash=_interopRequireDefault(require("lodash")),_cryptoJs=_interopRequireDefault(require("crypto-js")),_configerer=require("configerer"),_exceptioner=_interopRequireDefault(require("../exceptioner")),_momentTimezone=_interopRequireDefault(require("moment-timezone")),_uuid=require("uuid"),_nodeHtmlParser=require("node-html-parser");String.format=function(){let e=[];for(let t=0,r=arguments.length;t<r;t++)e.push(arguments[t]);let t=e[0];return e.shift(),t.replace(/\{(\d+)\}/g,function(t,r){return e[r]})};class Utiller{removeAttributeBy(e,t=e=>_lodash.default.isUndefined(e)){for(const r in e)t(e[r])&&delete e[r]}getNumberOfNormalize(e,t=0){if(_lodash.default.isNumber(e))return e;try{const r=_lodash.default.toNumber(e);return _lodash.default.isNumber(r)&&!isNaN(r)?r:t}catch(e){Util.appendError(`448561684561 ${e.message}`)}return t}getStringOfNormalize(e,t="",r=!1){if(_lodash.default.isString(e))return r?_lodash.default.trim(e):e;try{const a=_lodash.default.toString(e);return this.isOrEquals(a,"","undefined")?t:r?_lodash.default.trim(a):a}catch(e){Util.appendError(`448616845453 ${e.message}`)}return t}isValidVersionOfString(e){if(this.isUndefinedNullEmpty(e))return!1;const t=e.split(".");for(const e of t){const t=_lodash.default.toNumber(e);if(!_lodash.default.isNumber(t)||isNaN(t))return!1}return!0}getSeparatorOfUnique(){return"།།"}getStringOfVersionIncrement(e,t=1){const r=e.split(".").map(e=>_lodash.default.toNumber(e)),a=r.length-1;return r[a]=r[a]+t,r.join(".")}setLocaleOfMoment(e="en"){_momentTimezone.default.locale(e)}getUuidOfV4(){return(0,_uuid.v4)()}constructor(){(0,_defineProperty2.default)(this,"mapOfIdNTimeoutId",{}),(0,_defineProperty2.default)(this,"getEnvironment",()=>this.env),(0,_defineProperty2.default)(this,"isProductionEnvironment",()=>_lodash.default.isEqual(this.getEnvironment(),"prod")),(0,_defineProperty2.default)(this,"asyncUnitTaskFunction",(e=2e3,t="預設的param",r)=>async(a=this.getRandomHash(10))=>{const n=this.getRandomValue(e,1.2*e);try{const e=n;if(this.appendInfo(`before executed ===> i'm symbol of ${e}, ready to be executed, inner param = ${t}`),await this.syncDelay(n),_lodash.default.isFunction(r)&&r(a))throw Error("force to made error happen");return this.appendInfo(`after executed ===> i'm symbol of ${e}, the task cost ${n} million-seconds ${a?`i hav params ===> ${a}`:""}`),{randomValue:n,symbol:e,param:a}}catch(e){this.appendError(new Error(`asyncUnitTask() catch error ${e.message}`))}finally{this.appendInfo("wow.... finally got you")}}),(0,_defineProperty2.default)(this,"test",e=>async()=>(await Util.syncDelay(3e3),await Util.syncDelay(4e3),await Util.syncDelay(5e3),await Util.syncDelay(6e3),`3423809432804 ${e}`)),(0,_defineProperty2.default)(this,"findLowestValue",(e,t="price")=>{const r=_lodash.default.minBy(e,t)[t];return Math.floor(r)}),(0,_defineProperty2.default)(this,"findHighestValue",(e,t="price")=>{const r=_lodash.default.maxBy(e,t)[t];return Math.floor(r)}),(0,_defineProperty2.default)(this,"getStringOfValueRange",(e,t="price",r="$")=>{const a=_lodash.default.minBy(e,t)[t],n=_lodash.default.maxBy(e,t)[t];return n===a?`$${a}`:`${r}${a} - ${r}${n}`}),(0,_defineProperty2.default)(this,"getCallersName",()=>{let e;try{throw new Error}catch(t){let r,a=/(\w+)@|at (\w+) \(/g,n=t.stack;a.exec(n),r=a.exec(n),_lodash.default.isNull(r)||(e=r[1]||r[2])}return _lodash.default.startsWith("asyncGeneratorStep",e)&&(e=""),e}),(0,_defineProperty2.default)(this,"getRandomValue",(e,t)=>(e=Math.ceil(e),t=Math.floor(t),Math.floor(Math.random()*(t-e+1))+e)),(0,_defineProperty2.default)(this,"insertToArray",(e,t,...r)=>{if(!Array.isArray(e))throw new Error("First argument must be an array.");const a=Math.max(0,Math.min(t+1,e.length));e.splice(a,0,...r)}),(0,_defineProperty2.default)(this,"getStringOfYearADConvertToMinguoYear",(e,t=!1)=>{const r=e-1911;return r>0?`${t?"民國":""}${r}${t?"年":""}`:`${t?"民國":""}前${Math.abs(r)}${t?"年":""}`}),(0,_defineProperty2.default)(this,"convertDateToTimestamp",e=>(0,_momentTimezone.default)(e).valueOf()),(0,_defineProperty2.default)(this,"measureExecutionTime",async(e,...t)=>{const r=Date.now();await e(...t);const a=Date.now()-r,n=_momentTimezone.default.duration(a,"milliseconds"),i=Math.floor(n.asHours()),o=n.minutes(),s=n.seconds(),l=n.milliseconds(),u=(a/1e3).toFixed(3);this.appendInfo(`${i}小時 ${o}分 ${s}.${l.toString().padStart(3,"0")}秒 (合計 ${u} 秒)`)}),(0,_defineProperty2.default)(this,"formatPriceWithCurrency",(e,t)=>{if("number"!=typeof e||"string"!=typeof t)throw new TypeError("Invalid input: number must be a number and locale must be a string.");return new Intl.NumberFormat(t,{style:"currency",currency:new Intl.Locale(t).maximize().currency||"USD",minimumFractionDigits:0}).format(e)}),(0,_defineProperty2.default)(this,"formatPrice",(e,t)=>{if("number"!=typeof e)throw new TypeError("Invalid input: number must be a number.");return t?new Intl.NumberFormat(t,{style:"currency",currency:new Intl.Locale(t).maximize().currency||"USD",minimumFractionDigits:0}).format(e):e.toLocaleString("en-US")}),(0,_defineProperty2.default)(this,"generateUniversalKeywords",(e,t=50,r=4)=>{if(!e||"string"!=typeof e)return[];if(void 0===_lodash.default)return[];let a=e.trim(),n=[];r=Math.max(2,r),a.length>t&&(a=a.substring(0,t)),(a.match(/[a-zA-Z]+/g)||[]).forEach(e=>{if(e.length>=2){n.push(e.toUpperCase()),n.push(e.toLowerCase());const t=e.charAt(0).toUpperCase()+e.slice(1).toLowerCase();n.push(t)}});const i=(a.match(/\b[0-9]+[a-zA-Z]{1,4}\b|\b[0-9]{1,3}(w|ml|g|oz|k)\b|[\u4e00-\u9fa5a-zA-Z0-9]{1,4}(色|號)[\u4e00-\u9fa5a-zA-Z0-9]{0,2}/gi)||[]).filter(e=>e.length>=2).map(e=>e.toLowerCase());n.push(...i);let o=a.toLowerCase();o=o.replace(/[0-9]+([\u4e00-\u9fa5a-z]{1,4}|[\/\-\~\\])/g," ").replace(/\b[a-z]{1,4}\b/g," ").replace(/\b[0-9]{1,3}\b/g," "),o=o.replace(/[!@#$%^&*()_+={}\[\]:;"'<>,.?\/\\|`~]/g," ").replace(/系列|一組|單色|多款|套組|全套|專用|迷你|頂級|高品質|超閃|奢華|最新|款式|新款|超亮|的|與|和|閃|美甲/g," ").replace(/\s+/g,"").trim();for(let e=2;e<=r;e++)for(let t=0;t<=o.length-e;t++){const r=o.substring(t,t+e);n.push(r)}return _lodash.default.chain(n).filter(e=>e.length>=2).filter(e=>e.length>2||!/^[\u4e00-\u9fa5a-z0-9]$/.test(e)).uniq().sortBy().value()}),(0,_defineProperty2.default)(this,"mutateIndexOfArrayItem",(e,t,r=0)=>{if(!Array.isArray(e)||!_lodash.default.isObject(t))return e;const a=_lodash.default.findIndex(e,e=>_lodash.default.isEqual(e,t));if(-1===a)return e;e.splice(a,1);const n=_lodash.default.clamp(r,0,e.length);return e.splice(n,0,t),e}),(0,_defineProperty2.default)(this,"getArrayOfModifyObject2Index",(e,t,r=0)=>{if(!Array.isArray(e)||!_lodash.default.isObject(t))return e;const a=_lodash.default.cloneDeep(e),n=_lodash.default.findIndex(a,e=>_lodash.default.isEqual(e,t));if(-1===n)return e;a.splice(n,1);const i=_lodash.default.clamp(r,0,a.length);return a.splice(i,0,t),a}),(0,_defineProperty2.default)(this,"generateLabelValuePairsWithOrigin",(e=[{label:"aa",value:1203},{label:"cc",value:1204},{label:"gg",value:2}],t=["aa","bb"])=>{const r=new Set(e.map(e=>e.value));return _lodash.default.chain(t).uniq().map(t=>{const a=_lodash.default.find(e,{label:t});if(a)return{label:t,value:a.value};let n;do{n=_lodash.default.random(2,999999999)}while(r.has(n));return r.add(n),{label:t,value:n}}).value()}),(0,_defineProperty2.default)(this,"getItemsOfMarkMatching",(e=[],t=[],r="value",a="belong")=>{const n=new Set(t);return _lodash.default.map(e,e=>({...e,[a]:n.has(e[r])}))}),(0,_defineProperty2.default)(this,"generateVariants",(e,t="|",r="-")=>{const a=e.filter(e=>e.length>0);return 0===a.length?[]:1===a.length?a[0].map(e=>({label:e.label,value:e.value})):_lodash.default.reduce(a,(e,t)=>_lodash.default.flatMap(e,e=>t.map(t=>[...e,t])),[[]]).map(e=>({label:e.map(e=>e.label).join(t),value:e.map(e=>e.value).join(r)}))}),(0,_defineProperty2.default)(this,"renameKeysInArray",(e,...t)=>{const r=Object.fromEntries(t);return e.map(e=>_lodash.default.mapKeys(e,(e,t)=>r[t]||t))}),(0,_defineProperty2.default)(this,"getArrayOfMergeBySpecificId",(e,t,r="id")=>{if(!Array.isArray(t))return e;const a=_lodash.default.keyBy(t,e=>_lodash.default.get(e,r));return e.map(e=>{const t=_lodash.default.get(e,r),n=a[t];return n?_lodash.default.merge({},e,n):e})}),(0,_defineProperty2.default)(this,"toPercentageDecimal",e=>{if(_lodash.default.isNil(e))return 1;_lodash.default.isString(e)&&(e=e.replace(/%/g,"").trim());const t=_lodash.default.toNumber(e);return _lodash.default.isFinite(t)?_lodash.default.round(t/100,10):1}),(0,_defineProperty2.default)(this,"getNumberOfMultiplyCeil",(e,t,r=0)=>{const a=Math.pow(10,r);return Math.ceil(e*t*a)/a}),(0,_defineProperty2.default)(this,"generateGoogleCalendarLink",({title:e,startDate:t,startTime:r,endDate:a,endTime:n,location:i,details:o})=>{const s=(0,_momentTimezone.default)(`${t} ${r}`,"YYYY/MM/DD HH:mm").format("YYYYMMDDTHHmmss"),l=(0,_momentTimezone.default)(`${a} ${n}`,"YYYY/MM/DD HH:mm").format("YYYYMMDDTHHmmss"),u=new URLSearchParams;return e&&u.append("text",e),s&&l&&u.append("dates",`${s}/${l}`),o&&u.append("details",o),i&&u.append("location",i),u.append("ctz","Asia/Taipei"),u.append("trp","true"),`https://calendar.google.com/calendar/r/eventedit?${u.toString()}`}),(0,_defineProperty2.default)(this,"generateTimeTreeLink",({title:e,startDate:t,startTime:r,endDate:a,endTime:n,location:i,memo:o})=>{const s=new URLSearchParams;return e&&s.append("title",e),t&&s.append("start_date",t.replace(/\//g,"-")),r&&s.append("start_time",r),a&&s.append("end_date",a.replace(/\//g,"-")),n&&s.append("end_time",n),i&&s.append("location",i),o&&s.append("memo",o),`https://timetreeapp.com/plans/new?${s.toString()}`}),(0,_defineProperty2.default)(this,"generateIcsContent",({title:e,startDate:t,startTime:r,endDate:a,endTime:n,location:i,details:o})=>{const s=_momentTimezone.default.tz(`${t} ${r}`,"YYYY/MM/DD HH:mm","Asia/Taipei").format("YYYYMMDDTHHmmss"),l=_momentTimezone.default.tz(`${a} ${n}`,"YYYY/MM/DD HH:mm","Asia/Taipei").format("YYYYMMDDTHHmmss"),u=(0,_momentTimezone.default)().utc().format("YYYYMMDDTHHmmss")+"Z",d=e=>e.replace(/\\/g,"\\\\").replace(/,/g,"\\,").replace(/;/g,"\\;").replace(/\n/g,"\\n"),f=["BEGIN:VCALENDAR","VERSION:2.0","PRODID:-//Gemini AI//NONSGML v1.0//EN","BEGIN:VEVENT","UID:"+(Date.now().toString(36)+Math.random().toString(36).substring(2,5)+"@gemini-app.com"),`DTSTAMP:${u}`];return s&&f.push(`DTSTART;TZID=Asia/Taipei:${s}`),l&&f.push(`DTEND;TZID=Asia/Taipei:${l}`),e&&f.push(`SUMMARY:${d(e)}`),i&&f.push(`LOCATION:${d(i)}`),o&&f.push(`DESCRIPTION:${d(o)}`),f.push("END:VEVENT","END:VCALENDAR"),f.join("\r\n")}),(0,_defineProperty2.default)(this,"generateAllCalendarLinks",e=>{const t=this.generateGoogleCalendarLink(e),r={...e,memo:e.details},a=this.generateTimeTreeLink(r),n=this.generateIcsContent(e);return{google:t,timeTree:a,ics:`data:text/calendar;charset=utf8,${encodeURIComponent(n)}`}}),(0,_defineProperty2.default)(this,"getArrayOfMappingRef",(e,t)=>_lodash.default.map(e,e=>{const r=_lodash.default.find(t,{value:e.value});return r?_lodash.default.merge({},e,r):e})),(0,_defineProperty2.default)(this,"areAllValuesTheSameOnKeys",(e,...t)=>{if(!e||e.length<=1||0===t.length)return!0;for(const r of t){const t=e[0]?.[r];if(!_lodash.default.every(e,e=>e[r]===t))return!1}return!0}),this.init(),this.env="dev"}performActionWithoutTimingIssue(e=()=>!0,t=10){this.syncDelay(t).then(()=>e())}executeTimeoutTask(e,t=1e3,r=this.getRandomHash(),...a){const n=this,i=this.mapOfIdNTimeoutId[r];i&&clearTimeout(i);const o=setTimeout(async(...t)=>{await e(),delete n.mapOfIdNTimeoutId[r]},t,...a);n.mapOfIdNTimeoutId[r]=o}printLogMessage(e,t=!1,...r){this.isProductionEnvironment()||(t?this.appendError(e,...r):this.appendInfo(e,...r))}init(){}setEnvironment(e){this.env=e}appendInfo(...e){this.isProductionEnvironment()}appendError(...e){this.isProductionEnvironment()}async syncDelay(e=2e3){return new Promise(t=>{setTimeout(()=>{t(e)},e)})}startWithRegex(e="",t="."){return new RegExp(`^${t}`,"i").test(e)}accumulate(e,t){let r=e;for(const e of t)void 0!==e&&_lodash.default.isFunction(e)&&(r=e(r));return r}isOrEquals(e,...t){for(const r of t)if(_lodash.default.isEqual(e,r))return!0;return!1}isAndEquals(...e){for(const t of e)if(!t())return!1;return!0}getStringOfHeadMatch(e,t,r="g"){const a=e.match(new RegExp(t,r));return this.isUndefinedNullEmpty(a)?void 0:a[0]}or(...e){for(const t of e)if(_lodash.default.isBoolean(t)&&t)return!0;return!1}and(...e){for(const t of e)if(!t)return!1;return!0}nth(e,t=-1){return _lodash.default.nth(e,t%_lodash.default.size(e))}getExistOne(...e){for(const t of e)if(t)return t}getStringOfDropHeadSign(e,t){return _lodash.default.dropWhile(Array.from(e),e=>_lodash.default.isEqual(e,t)).join("")}isAndWith(e,t,...r){for(const a of r)if(!t(e,a))return!1;return!0}async syncDelayRandom(e=3e3,t=5e3){const r=this.getRandomValue(e,t);return await this.syncDelay(r),r}has(e,t,r=!1){return _lodash.default.isArray(e)?r?_lodash.default.findIndex(e,e=>_lodash.default.isEqual(t,e))>-1:_lodash.default.indexOf(e,t)>-1:_lodash.default.isObject(t)?e[t]:!!_lodash.default.isString(e)&&e.indexOf(t)>-1}containsBy(e,t){return _lodash.default.findIndex(e,e=>_lodash.default.isEqual(e,t))>=0}getStringOfInsideParentheses(e,t="."){return this.getStringOfRule(e,t,"(",")")}getStringOfInsideBrackets(e,t="."){return this.getStringOfRule(e,t,"[","]")}getStringOfInsideBraces(e,t="."){return this.getStringOfRule(e,t,"{","}")}getStringOfRule(e,t=".",r="{",a="}"){return this.getStringOfHeadMatch(e,`(?<=\\${r})${t}+?(?=\\${a})`)}getRandomHash(e=20){const t=_cryptoJs.default.lib.WordArray.random(e);return _cryptoJs.default.enc.Base64.stringify(t).substring(0,e)}getRandomHashV2(e){let t="";for(let r=0;r<e;r++){const e=Math.floor(62*Math.random());t+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(e)}return t}getEncryptString(e,t=_configerer.configerer.ENCRYPT_KEY,r=!1){if(t.length>22)throw new _exceptioner.default(8010,_lodash.default.size(t));const a=_cryptoJs.default.enc.Base64.parse("thisIsIVWeNeedToGenerateTheSameValue"),n=r?_cryptoJs.default.enc.Base64.parse(`${t}${_lodash.default.range(0,22-t.length).join("")}`):t;return _cryptoJs.default.AES.encrypt(e,n,{iv:a}).toString()}getEncryptStringV2(e,t=_configerer.configerer.ENCRYPT_KEY,r=!1){if(t.length>22)throw new _exceptioner.default(8010,_lodash.default.size(t));const a=_cryptoJs.default.enc.Base64.parse("thisIsIVWeNeedToGenerateTheSameValue"),n=r?_cryptoJs.default.enc.Base64.parse(`${t}${_lodash.default.range(0,22-t.length).join("")}`):t;return _cryptoJs.default.AES.encrypt(JSON.stringify({content:e}),n,{iv:a}).toString()}getDecryptString(e,t=_configerer.configerer.ENCRYPT_KEY){if(t.length>22)throw new _exceptioner.default(8010,_lodash.default.size(t));const r=_cryptoJs.default.enc.Base64.parse("thisIsIVWeNeedToGenerateTheSameValue");try{const a=_cryptoJs.default.AES.decrypt(e,t,{iv:r}).toString(_cryptoJs.default.enc.Utf8);if(!_lodash.default.isEmpty(a.trim()))return a}catch(e){}return _cryptoJs.default.AES.decrypt(e,_cryptoJs.default.enc.Base64.parse(`${t}${_lodash.default.range(0,22-t.length).join("")}`),{iv:r}).toString(_cryptoJs.default.enc.Utf8)}getDecryptStringV2(e,t=_configerer.configerer.ENCRYPT_KEY){if(t.length>22)throw new _exceptioner.default(8010,_lodash.default.size(t));const r=_cryptoJs.default.enc.Base64.parse("thisIsIVWeNeedToGenerateTheSameValue");try{const a=_cryptoJs.default.AES.decrypt(e,t,{iv:r}).toString(_cryptoJs.default.enc.Utf8);if(!_lodash.default.isEmpty(a.trim()))return JSON.parse(a).content}catch(e){}const a=_cryptoJs.default.AES.decrypt(e,_cryptoJs.default.enc.Base64.parse(`${t}${_lodash.default.range(0,22-t.length).join("")}`),{iv:r}).toString(_cryptoJs.default.enc.Utf8);return JSON.parse(a).content}getFirebaseFormattedString(e){return _lodash.default.replace(e,/[\.\#\$\[\]]/g,"-").trim()}formalizeNamesToArray(e){let t=e;for(t=t.split(_configerer.configerer.SEPARATE_TONE_SINGER)[0].trim(),t=_lodash.default.replace(t,/[,\/#!$%\^&\*;:{}=_`、~()()]/g,"_").trim(),t=this.getFirebaseFormattedString(t),t=_lodash.default.replace(t,/\_\_+/g,"_").trim();_lodash.default.endsWith(t,"_");)t=t.slice(0,-1).trim();const r=t.split("_");return _lodash.default.map(r,e=>_lodash.default.trim(e))}getShuffledArrayWithLimitCountHighPerformance(e,t){let r=new Array(t),a=e.length,n=new Array(a);for(t>a&&(t=a);t--;){let i=Math.floor(Math.random()*a);r[t]=e[i in n?n[i]:i],n[i]=--a in n?n[a]:a}return r}getFileNameFromPath(e,t=!1){const r=e.split("/").pop();return t?r:r.split(".").shift()}getFileNameExtensionFromPath(e){return e.split("/").pop()}getPathOfReplaceLastDir(e,t){const r=e.split("/");return r.pop(),r.push(t),r.join("/")}getExtensionFromPath(e){const t=e.split("/").pop().split(".");return _lodash.default.size(t)>1?t.pop():""}getFolderPathOfSpecificPath(e){const t=e.split("/");return t.pop(),t.join("/")}getFolderNameOfFilePath(e){if(this.isValidFilePath(e)){const t=e.split("/");return _lodash.default.nth(t,-2)}throw new _exceptioner.default(9999,`64255615 path is not valid '${e}'`)}isUnderTargetPath(e,t){const r=e.split("/");return this.has(r,t)}getFileDirPath(e,t=!0){return _lodash.default.join(_lodash.default.initial(_lodash.default.split(e,"/")),"/")+(t?"/":"")}isPathEqualsFileType(e,t){const r=e.split(".").pop();return _lodash.default.isEqual(r,t)}isValidFilePath(e){const t=this.getExtensionFromPath(e);return _lodash.default.size(t)>0}getArrayOfSize(e,t=1){return _lodash.default.take(e,t)}getShuffledArrayWithLimitCount(e,t){return this.getShuffledArrayWithLimitCountHighPerformance(e,t)}getRandomItemOfArray(e,...t){if(!_lodash.default.isArray(e))throw new _exceptioner.default(9999,`why are you so stupid, typeof array should be array, not ==> ${e} `);const r=_lodash.default.without(e,...t),a=_lodash.default.size(r)>0?r:e,n=this.getShuffledArrayWithLimitCount(a,1);return n.length>0?n[0]:void 0}appendMapOfKeyArray(e,t,...r){this.isUndefinedNullEmpty(e[t])?e[t]=[...r]:e[t].push(...r)}getMergedArrayBy(e=[],t=[],r){if(!r||0===e.length||0===t.length)return[...e];const a=new Map(t.map(e=>[e[r],e]));return e.map(e=>({...a.get(e[r])||{},...e}))}getShuffledItemFromArray(e){return _lodash.default.shuffle(e)[0]}getShuffledArray(e){return _lodash.default.shuffle(e)}isJson(e){e="string"!=typeof e?JSON.stringify(e):e;try{e=JSON.parse(e)}catch(e){return!1}return"object"==typeof e&&null!==e}getObjectValue(e){return _lodash.default.isObject(e)?Object.values(e)[0]:""}getObject(e,t){const r={};return r[e]=t,r}getStringOfCreditCardFormatted(e=0){return e.replace(/\D/g,"").replace(/(\d{4})(?=\d)/g,"$1-").slice(0,19)}getObjectKey(e){return _lodash.default.isObject(e)?Object.keys(e)[0]:""}printf(){this.appendInfo("i can use in web || react.js")}isKeywordRule(e){if(_lodash.default.isUndefined(e)||_lodash.default.isEmpty(e))throw new Error("PARAMS CAN NOT BE EMPTY");if(!_lodash.default.isString(e))throw new Error("PARAMS SHOULD BE STRING");if(e.length>20)throw new Error("EXCEED 20 WORDS IS NOT ALLOWED")}getItsKeyByValue(e,t){return Object.keys(e).find(r=>e[r]===t)}startWiths(e,t=[]){for(const r of t)if(_lodash.default.startsWith(e,r))return!0;return!1}replaceAll(e,t,r){return _lodash.default.replace(e,new RegExp(`${t}`,"g"),r)}replaceAllWithSets(e="",...t){let r=e;for(const e of t){if(this.isOrEquals(void 0,e.from,e.to))throw(0,_exceptioner.default)(9999,"from or to can't be empty");r=this.replaceAll(r,e.from,e.to)}return r}replaceArrayByContentIndex(e,t,r){e[_lodash.default.indexOf(e,t)]=r}deepFlat(e,t="_"){let r="";const a=[[e,""]];for(;a.length>0;){const[e,n]=a.pop();if(_lodash.default.isArray(e))for(let t=e.length-1;t>=0;t--)a.push([e[t],n]);else if(_lodash.default.isObject(e)){const r=Object.keys(e);for(let i=r.length-1;i>=0;i--){const o=r[i];a.push([e[o],n+o+t])}}else{const a=_lodash.default.trim(String(e));a.length>0?r+=(r.length>0?t:"")+n+a:n.length>0&&r.length>0?r+=t:n.length>0&&0===r.length&&(r+=n.endsWith(t)?n.slice(0,-t.length):n)}}return r.endsWith(t)&&(r=r.slice(0,-t.length)),r}joinEscapeChar(e){return(e+"").replace(/[\\"']/g,"\\$&").replace(/\u0000/g,"\\0")}getValueWithIntegerType(e){try{const t=parseInt(e);return isNaN(t)?0:t}catch(e){return 0}}getValueOfPriority(...e){for(const t of e)if(!this.isUndefinedNullEmpty(t))return t}async asyncPool(e,t,r){const a=[],n=[];for(const i of t){const o=Promise.resolve().then(()=>r(i,t));if(a.push(o),e<=t.length){const t=o.then(()=>n.splice(n.indexOf(t),1));n.push(t),n.length>=e&&await Promise.race(n)}}return Promise.all(a)}getAttrValueInSequence(e,...t){for(const r of t)if(!_lodash.default.isEmpty(e[r]))return e[r];return e}toDBC(e){for(var t="",r=0;r<e.length;r++)32===e.charCodeAt(r)&&(t+=String.fromCharCode(12288)),e.charCodeAt(r)<127&&(t+=String.fromCharCode(e.charCodeAt(r)+65248));return t}toCDB(e){for(var t="",r=0;r<e.length;r++)12288!==e.charCodeAt(r)?e.charCodeAt(r)>65280&&e.charCodeAt(r)<65375?t+=String.fromCharCode(e.charCodeAt(r)-65248):t+=String.fromCharCode(e.charCodeAt(r)):t+=String.fromCharCode(e.charCodeAt(r)-12256);return t}findIndexes(e,t){const r=[];let a=!0,n=0;for(;a;)n=_lodash.default.findIndex(e,t,n+1),n>-1?r.push(n):a=!1;return r}getSliceArrayOfSpecificIndexes(e,...t){const r=[],a=_lodash.default.size(e);for(const n of t){if(!_lodash.default.isNumber(n))throw new _exceptioner.default(9999,`59941278 index should be number => ${n}, ${e}`);if(n>a-1)throw new _exceptioner.default(9999,`5994123 index=>${n} is not valid, exceed than array size=${a}, ${e}`);r.push(_lodash.default.nth(e,n))}return r}indexesOf(e,t){const r=[];let a=-1;for(;-1!==(a=e.indexOf(t,a+1));)r.push(a);return r}getIndexOfContext(e,t){return _lodash.default.findIndex(e,e=>_lodash.default.isEqual(e.trim(),t))}toOneLineString(e){return _lodash.default.join(_lodash.default.split(e,"\n"),"")}toSpaceLessString(e){return _lodash.default.split(e,"").map(e=>_lodash.default.trim(e)).join("")}toNewLineLessString(e){return _lodash.default.split(e,"\n").map(e=>_lodash.default.trim(e)).join("")}exist(e){return!_lodash.default.isNull(e)&&!_lodash.default.isUndefined(e)}isUndefinedNullEmpty(e){const t=null==e,r=!!(_lodash.default.isString(e)||_lodash.default.isArray(e)||_lodash.default.isObject(e))&&_lodash.default.isEmpty(e);return t||r}isAndConditionOfUndefinedNullEmpty(...e){for(const t of e)if(!this.isUndefinedNullEmpty(t))return!1;return!0}isOrConditionOfUndefinedNullEmpty(...e){for(const t of e)if(this.isUndefinedNullEmpty(t))return!0;return!1}getStringHandledByEachLine(e,t=(e,t,r)=>!0,r="\n"){const a=e.split(r);for(const e of a)t(e,_lodash.default.indexOf(a,e),a);return a.join(r)}getSegmentsOfEachLine(e){return e.split("\n")}getNormalizedStringEndWith(e,t){e=this.toCDB(e),t=this.toCDB(t);const r=_lodash.default.join(_lodash.default.dropRightWhile(e,e=>!_lodash.default.isEqual(e,t)),"");return _lodash.default.isEmpty(r)?e:r}getNormalizedStringNotStartWith(e,...t){e=this.toCDB(e);const r=_lodash.default.join(_lodash.default.dropWhile(e,e=>this.has(t,e)),"");return _lodash.default.isEmpty(r)?e:r}getNormalizedStringNotEndWith(e,...t){e=this.toCDB(e);const r=_lodash.default.join(_lodash.default.dropRightWhile(e,e=>this.has(t,e)),"");return _lodash.default.isEmpty(r)?e:r}getTodayTimeFormat(e){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format("YYYY-MM-DD")}getCustomFormatOfDatePresent(e,t="YY/MM"){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format(t)}getSimpleDateYYMMDDFormat(e){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format("YY/MM/DD")}getSimpleTimeYYMMDDHHmmFormat(e){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format("YY/MM/DD HH:mm")}getECPayCurrentTimeFormat(e){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format("YYYY/MM/DD HH:mm:ss")}getCurrentTimeFormatV2(e){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format("YYYY/MM/DD HH:mm:ss")}getCurrentTimeFormatYMDHM(e){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format("YYYY/MM/DD HH:mm")}getCurrentTimeFormatYMDHMS(e){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format("YYYY/MM/DD HH:mm:ss")}getCurrentTimeFormat(e){return(0,_momentTimezone.default)(e||this.getCurrentTimeStamp()).format("YYYY-MM-DD-HH-mm-ss")}getCurrentMillionSecTimeFormat(e){return(0,_momentTimezone.default)(e||void 0).format("YYYY-MM-DD-HH-mm-ss-SSS")}isBetweenTimeStamp(e=this.getCurrentTimeStamp(),t,r){return(0,_momentTimezone.default)(e).isBetween(t,r)}isBeforeTimeStamp(e=this.getCurrentTimeStamp(),t){return(0,_momentTimezone.default)(e).isBefore(t)}isAfterTimeStamp(e=this.getCurrentTimeStamp(),t){return(0,_momentTimezone.default)(e).isAfter(t)}formatTimeByLocale(e,t="zh-TW",r="Asia/Taipei",a=!0){_momentTimezone.default.locale(t);const n=a?"YYYY/MM/DD HH:mm":"YYYY/MM/DD hh:mm A";return(0,_momentTimezone.default)(e).tz(r).format(n)}getTimeStampWithConditions(e={days:0,months:0,years:0,minutes:0,seconds:0,hours:0},t=this.getCurrentTimeStamp()){let r=(0,_momentTimezone.default)(t);for(const t in e){const a=e[t],n=t;a>0&&(r=r.add(a,n)),a<0&&(r=r.subtract(Math.abs(a),n))}return r.valueOf()}getTimeStampByStringFormat(e){return this.getTimeStampFromSpecificFormat(e,"YYYY/MM/DD HH:mm:ss").valueOf()}getTimeStampFromSpecificFormat(e,t="YYYY/MM/DD HH:mm:ss"){return(0,_momentTimezone.default)(e,t).valueOf()}getTimeStampFromECPayStringFormat(e){return this.getTimeStampFromSpecificFormat(e,"YYYY/MM/DD HH:mm:ss").valueOf()}getTimeFormatOfDurationToMillionSecond(e){return _momentTimezone.default.utc(e).format("HH小時mm分鐘ss秒SSS")}getTimeFormatOfDurationToSecond(e){return _momentTimezone.default.utc(e).format("HH小時mm分鐘ss秒")}getTimeFormatOfDurationToDay(e){return _momentTimezone.default.utc(e).format("DD天HH小時mm分鐘ss秒")}getChineseTimeFormat(e){return _momentTimezone.default.locale("zh-TW"),(0,_momentTimezone.default)(e).format("LLLL")}getMinuteFormatOfDuration(e){return _momentTimezone.default.duration(e).asMinutes()}getSecondFormatOfDuration(e){return _momentTimezone.default.duration(e).asSeconds()}getDayFormatOfDuration(e){return _momentTimezone.default.duration(e).asDays()}getDurationOfMillionSec(e){const t=this.getCurrentTimeStamp(),r=(0,_momentTimezone.default)(e).valueOf(),a=_lodash.default.sortBy([{ts:t},{ts:r}],e=>e.ts).map(e=>e.ts);let n=(0,_momentTimezone.default)(a.pop()),i=(0,_momentTimezone.default)(a.shift());return _momentTimezone.default.duration(n.diff(i)).asMilliseconds()}getCurrentTimeStamp(){return(0,_momentTimezone.default)().valueOf()}isStringContainInLines(e,t){for(let r of _lodash.default.split(e,"\n"))if(this.has(r,t))return!0;return!1}camel(...e){return _lodash.default.camelCase(e.join("_"))}upperCamel(...e){return _lodash.default.upperFirst(this.camel(...e))}array2Obj(e){const t={};for(const r of e)t[`${this.getObjectKey(r)}`]=this.getObjectValue(r);return t}arrayToObjWith(e,t){const r={};for(const a of e){const e=t(a),n=r[e];n&&_lodash.default.isArray(n)?n.push(a):r[e]=[a]}return r}isEmptyString(e){return _lodash.default.isEqual(_lodash.default.trim(e),"")}mergeObject(...e){return _lodash.default.merge(...e)}syncSetTimeout(e,t,r=()=>{}){!function a(n){n?r():setTimeout(function(){e(),a(!0)},t)}()}mergeArrayBy(e="id",...t){return Object.values(t.flat().reduce((t,r)=>(r[e]&&(t[r[e]]={...t[r[e]]||{},...r}),t),{}))}getRelativePath(e,t){return _lodash.default.dropWhile(e,(e,r)=>_lodash.default.isEqual(e,t[r])).join("")}dropItemsByIndex(e,t,r){_lodash.default.remove(e,(e,a,n)=>r>=a&&a>=t)}isEven(e){return e%2==0}isOdd(e){return 1===Math.abs(e%2)}enrichZhTw(){_momentTimezone.default.locale("zh-tw",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah點mm分",LTS:"Ah點m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日Ah點mm分",LLLL:"YYYY年MMMD日ddddAh點mm分",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日Ah點mm分",llll:"YYYY年MMMD日ddddAh點mm分"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){let r=e;return 12===r&&(r=0),"凌晨"===t||"早上"===t||"上午"===t?r:"下午"===t||"晚上"===t?r+12:r>=11?r:r+12},meridiem:function(e,t,r){const a=100*e+t;return a<600?"凌晨":a<900?"早上":a<1130?"上午":a<1230?"中午":a<1800?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[點整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[點整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[點整]":"[昨天]LT"},nextWeek:function(){let e,t;return e=(0,_momentTimezone.default)().startOf("week"),t=this.diff(e,"days")>=7?"[下]":"[本]",0===this.minutes()?t+"dddA點整":t+"dddAh點mm"},lastWeek:function(){let e,t;return e=(0,_momentTimezone.default)().startOf("week"),t=this.unix()<e.unix()?"[上]":"[本]",0===this.minutes()?t+"dddAh點整":t+"dddAh點mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"周";default:return e}},relativeTime:{future:"%s内",past:"%s前",s:"幾秒",m:"1 分鐘",mm:"%d 分鐘",h:"1 小時",hh:"%d 小時",d:"1 天",dd:"%d 天",M:"1 個月",MM:"%d 个月",y:"1 年",yy:"%d 年"},week:{dow:1,doy:4}})}getVisibleOrHidden(e){return{visibility:e?"visible":"hidden"}}getNumberOfPercentageToFloat(e){let t=e.replace("%","");return parseFloat(t)/100}getVisibleOrNone(e,t=!1){return{display:e?t?"flex":"inherit":"none"}}stringToInteger(e){switch(e=_lodash.default.toUpper(e)){case"A":return 0;case"B":return 1;case"C":return 2;case"D":return 3;case"E":return 4;case"F":return 5;case"G":return 6;case"H":return 7;case"I":return 8;case"J":return 9;case"K":return 10;case"L":return 11;case"M":return 12;case"N":return 13;default:return 101}}integerToString(e){switch(e){case 0:return"A";case 1:return"B";case 2:return"C";case 3:return"D";case 4:return"E";case 5:return"F";case 6:return"G";case 7:return"H";case 8:return"I";case 9:return"J";case 10:return"K";case 11:return"L";case 12:return"M";case 13:return"N";default:return"Z"}}toObjectMap(e,...t){const r=[];for(const a of e){const e={};for(const r of t){const t=r.func?r.func:e=>e;e[r.to]=this.isUndefinedNullEmpty(r.from)||!_lodash.default.isObject(a)?t(a):t(a[r.from])}r.push(e)}return r}exeAll(e,...t){if(_lodash.default.isArray(e))for(const r of e)for(const e of t)e(r);else{if(!_lodash.default.isObject(e))throw new _exceptioner.default(9999,"7841212 type can't be array or object");for(const r in e)for(const a of t)e[r]=a(e[r])}return e}getObjectWhile(e,t,r=e=>!0){const a={};for(const n in e)r(e,t,n)&&(a[n]=e[n]);return a}getIntersectionObject(e,t){return this.getObjectWhile(e,t,(e,t,r)=>void 0!==t[r])}getDifferenceObject(e,t){return this.getObjectWhile(e,t,(e,t,r)=>void 0===t[r])}isObjectContainAndEqual(e,t){let r=!0;for(const a in e)if(void 0===t[a]||t[a]!==e[a]){r=!1;break}return r}getStringOfPop(e,t){if(!_lodash.default.isString(e))throw new _exceptioner.default(9999,"445115,type should be string but ==> "+typeof e);const r=e.split(t);return r.pop(),r.join(t)}getStringOfShift(e,t){if(!_lodash.default.isString(e))throw new _exceptioner.default(9999,"445116,type should be string but ==> "+typeof e);const r=e.split(t);return r.shift(),r.join(t)}toObjectWithAttributeKey(e,t){const r={};for(const a of e){const e=a[t];if(this.isUndefinedNullEmpty(e))throw new _exceptioner.default(9999,`48157232 pk can't be empty => '${e}'`);r[e]=a}return r}getObjectOfArraySpecifyAttr(e,t){return this.toObjectWithAttributeKey(e,t)}getStateOfStringContainsSign(e,...t){for(const r of t)if(this.has(e,r))return{exists:!0,sign:r};return{exists:!1}}constraintOfParam(e,t,...r){let a=!1;const n=!!_lodash.default.isEmpty(r)||this.and(...r.map(e=>e.logic));switch(t){case"array":_lodash.default.isArray(e)&&n&&(a=!0);break;case"object":_lodash.default.isObject(e)&&n&&(a=!0);break;case"string":_lodash.default.isString(e)&&n&&(a=!0);break;case"number":_lodash.default.isNumber(e)&&n&&(a=!0);break;case"other":if(n)return!0}const i=_lodash.default.isEmpty(r)?"":`, ${r.map(e=>e.message).join(" | ")}`;if(!1===a)throw new _exceptioner.default(9999,`7474423 type should be ${t} but get '${typeof t}' ${i} `)}getSliceArrayWithMutate(e,t){return _lodash.default.remove(e,(e,r)=>r<t)}getArrayOfInteraction(e,t){return e.filter(e=>!t.includes(e))}getArrayOfMoveToSpecificIndex(e,t,r){if(!Array.isArray(e))throw new Error("First argument must be an array.");const a=e.length;if(t<0||t>=a||r<0||r>=a)return[...e];if(t===r)return[...e];const n=[...e],[i]=n.splice(t,1);return n.splice(r,0,i),n}getArrayOfMoveItemToSpecificIndex(e,t,r){const a=_lodash.default.indexOf(e,t);return this.getArrayOfMoveToSpecificIndex(e,a,r)}getArrayOfMoveSpecificItemToAside(e,t,r=!0){const a=_lodash.default.indexOf(e,t);return this.getArrayOfMoveSpecificIndexToAside(e,a,r)}getArrayOfMoveSpecificIndexToAside(e,t,r=!0){const a=_lodash.default.size(e)-1;return this.getArrayOfMoveToSpecificIndex(e,t,r?a:0)}getECPayCheckMacValue(e,t="5294y06JbISpM5x9",r="v77hoKGq4kWxNNIS"){const a=_lodash.default.cloneDeep(e);delete a.CheckMacValue;const n=Object.keys(a).sort((e,t)=>e>t?1:-1);let i="";for(const e of n)i+=`${e}=${a[e]}&`;return i=`HashKey=${t}&${i}HashIV=${r}`,i=encodeURIComponent(i).toLowerCase(),i=i.replace(/%20/g,"+").replace(/%2d/g,"-").replace(/%5f/g,"_").replace(/%2e/g,".").replace(/%21/g,"!").replace(/%2a/g,"*").replace(/%28/g,"(").replace(/%29/g,")").replace(/%20/g,"+"),_lodash.default.toUpper(_cryptoJs.default.SHA256(i).toString(_cryptoJs.default.enc.Hex))}getStringOfHandledHtml(e,t=e=>{}){const r=(0,_nodeHtmlParser.parse)(e);return t(r),r.toString()}getSpecifyObjectBy(e,t){for(const r of e)if(t(r))return r}validatePayloadObjectValid(e,t=[],r=this.getRandomHash(10)){if(this.isUndefinedNullEmpty(e))throw new _exceptioner.default(9999,`${r} content(pay-load) is undefined || empty`);for(const a of t)if(_lodash.default.isString(a)){if(this.isUndefinedNullEmpty(e[a]))throw new _exceptioner.default(9999,`${r} ATTRIBUTE:'${a}' is not Exist`)}else if(_lodash.default.isObject(a)){const t=this.getObjectKey(a);if(!this.getObjectValue(a)(e[t]))throw new _exceptioner.default(9999,`${r} ATTRIBUTE:'${t}' is not valid of custom rule`)}return!0}getArrayOfSummarizeBy(e,t,r){const a={};for(const n of e){const e=n[t];void 0!==a[e]?a[e]=a[e]+n[r]:a[e]=n[r]}const n=[];for(const e in a){const i={};i[t]=e,i[r]=a[e],n.push(i)}return n}getHeadStringSplitBy(e,t=this.getSeparatorOfUnique()){return _lodash.default.split(e,t).shift()}getTailStringSplitBy(e,t=this.getSeparatorOfUnique()){return _lodash.default.split(e,t).pop()}getSlicesByIndexes(e=[],t=[]){const r=[];return _lodash.default.each(t,(a,n,i)=>{if(_lodash.default.isEqual(n,t.length-1))return!1;const o=_lodash.default.slice(e,a,t[n+1]);r.push(o)}),r}findIndexes(e,t){const r=[];let a=!0,n=0;for(;a;)n=_lodash.default.findIndex(e,t,n+1),n>-1?r.push(n):a=!1;return r}isOverSpecificAge(e,t=18){return(0,_momentTimezone.default)().diff((0,_momentTimezone.default)(e,"YYYY-MM-DD"),"years")>=t}isValidEmail(e){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}isValidTaiwaneseID(e){if(!/^[A-Z][1-2]\d{8}$/.test(e))return!1;const t=[1,9,8,7,6,5,4,3,2,1];let r=10*(e.charCodeAt(0)-65)+parseInt(e.slice(1));for(let a=0;a<t.length;a++)r+=parseInt(e.charAt(a+1))*t[a];return r%10==0}validatePersonalInfoInput(e,t,r,a,n,i=12){return e.length<2?{valid:!1,message:"姓名至少要兩個字"}:/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)?/^[A-Z][1-2]\d{8}$/.test(r)?/^09\d{8}$/.test(a)?this.isUndefinedNullEmpty(n)?{valid:!1,message:"出生日期格式不正確"}:(0,_momentTimezone.default)().diff(n,"years")<i?{valid:!1,message:`年齡不得小於 ${i} 歲`}:{valid:!0,message:"格式檢查通過"}:{valid:!1,message:"手機號碼格式不正確"}:{valid:!1,message:"身分證號碼格式不正確"}:{valid:!1,message:"電子郵件格式不正確"}}getStringOfFormatTimestampRange(e,t){const r=(0,_momentTimezone.default)(e),a=(0,_momentTimezone.default)(t),n=e=>e.format("YY/MM/DD");return r.year()===a.year()?`${n(r)} - ${a.format("MM/DD")}`:`${n(r)} - ${n(a)}`}getStringOfCalculateClassTime(e,t,r){const a=(0,_momentTimezone.default)(e),n=(0,_momentTimezone.default)(t).diff(a,"days")+1,i=Math.ceil(n/7)*r,o=Math.floor(i/60),s=i%60;return 0===s?`${o}小時`:`${o}小時${s}分鐘`}getNumberOfPeriodMinute(e,t){const r=(0,_momentTimezone.default)(e).format("HH:mm"),a=(0,_momentTimezone.default)(t).format("HH:mm"),n=(0,_momentTimezone.default)(r,"HH:mm"),i=(0,_momentTimezone.default)(a,"HH:mm");return _momentTimezone.default.duration(i.diff(n)).asMinutes()}getStringOfWeekTime(e,t,r){if(e<1||e>7)throw new Error("day 必須在 1 到 7 之間");const a=(0,_momentTimezone.default)(t).format("HH:mm"),n=(0,_momentTimezone.default)(r).format("HH:mm");return`${{1:"週一",2:"週二",3:"週三",4:"週四",5:"週五",6:"週六",7:"週日"}[e]} ${a}-${n}`}extractNumber(e){if(this.isUndefinedNullEmpty(e))return-1;const t=e.match(/\d+/);return t?Number(t[0]):-1}async fetchElementAttribute(e,t="innerText",r=""){return await e.evaluate(e=>e[t])}async fetchElementAttributes(e,t,r="",...a){const n=await e.$(t);if(!this.isUndefinedNullEmpty(n))try{return await n.evaluate((e,t)=>1===t.length?e[t.shift()]:{...t.map(t=>e[t])},a)}catch(e){return this.appendError(`1581532 ${t} fetch ${JSON.stringify(a)} fail, element is not found`),r}return r}async writeElementAttributes(e,t,...r){const a=await e.$(t);this.isUndefinedNullEmpty(a)?this.appendError(`1231232 ${t} fetch ${JSON.stringify(r)} fail, element is not found`):await a.evaluate((e,t)=>{t.map(t=>{const r=Object.entries(t),a=r[0][0],n=r[0][1];e[a]=n})},r)}getSliceArrayOfUnique(e){if(!Array.isArray(e)||0===e.length)return[];const t=e[0];if(_lodash.default.isObject(t)&&key){const t=new Map(e.map(e=>[e[key],e]));return Array.from(t.values())}return _lodash.default.isObject(t)?_lodash.default.uniqWith(e,_lodash.default.isEqual):Array.from(new Set(e))}getUniqueValuesBy(e,t="valueOfType"){return _lodash.default.uniq(e.map(e=>e[t]))}generateCombinations(...e){const t=e.map(e=>e.key),r=(_lodash.default.keyBy(e,"key"),e.map(e=>e.options.map(t=>({key:e.key,value:t.value,label:t.label})))),a=_lodash.default.reduce(r,(e,t)=>_lodash.default.flatMap(e,e=>t.map(t=>[...e,t])),[[]]).map(e=>{const t={},r=[],a=[];for(const{key:n,value:i,label:o}of e)t[n]=i,r.push(`${n}_${i}`),a.push(`${o}`);return{trait:t,id:r.join("_"),content:a.join("|")}});return _lodash.default.sortBy(a,e=>t.map(t=>e.trait[t]))}extractStaticSegments(e,t=[":"]){return e.trim().replace(/^\.?\/*|\/*$/g,"").split("/").filter(e=>e&&!t.some(t=>e.startsWith(t)))}mutateRemoveKeys(e,t){_lodash.default.forEach(e,(e,r)=>{const a=Object.fromEntries(Object.entries(e).filter(([e])=>!t.includes(e)));Object.keys(e).forEach(t=>delete e[t]),Object.assign(e,a)})}removeKeysFromArrayObjects(e,t){return _lodash.default.map(e,e=>Object.fromEntries(Object.entries(e).filter(([e])=>!t.includes(e))))}formatTextWithEllipsis(e,t){if(_lodash.default.size(e)<=t)return e;if(t<=6)return"";const r=t-6,a=Math.floor(r/2),n=r-a;return`${_lodash.default.truncate(e,{length:a,omission:""})}......${_lodash.default.takeRight(e,n).join("")}`}getObjectBy(e,t=e=>!0!==e.checked){return _lodash.default.fromPairs(_lodash.default.toPairs(e).filter(([e,r])=>t(r)))}mutateBy(e,t=e=>e){const r=_lodash.default.sortBy(e,t);e.splice(0,e.length,...r)}findUniqueStrings(...e){const t=_lodash.default.flatten(e),r=_lodash.default.countBy(t);return _lodash.default.chain(r).pickBy(e=>1===e).keys().compact().value()}getObjectOfSpecifyKey(e,t){const r={};return r[t]=e,r}findUniqueNonReferenceStrings(...e){if(0===e.length)return[];const[t,...r]=e,a=_lodash.default.flatten(r),n=_lodash.default.countBy(a);return _lodash.default.chain(n).pickBy((e,r)=>1===e&&!t.includes(r)).keys().compact().value()}getArrayOfFillMissingValues(e){const t=()=>_lodash.default.times(8,()=>_lodash.default.sample("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")).join(""),r=new Set(e.map(e=>e.value).filter(Boolean));return e.map(e=>{if(_lodash.default.isEmpty(e.value)){let a;do{a=t()}while(r.has(a));return r.add(a),{...e,value:a}}return e})}isFirestoreAutoId(e){return _lodash.default.isString(e)&&20===e.length&&/^[A-Za-z0-9]{20}$/.test(e)}getAutoIdOfFirestore(){return this.getRandomHashV2(20)}getStringOfConvertTimeRange(e){const[t,r]=e.split("|"),a=t.split("(")[0],[n,i]=r.split("-"),o=e=>(0,_momentTimezone.default)(`${a} ${e}`,"YYYY/MM/DD HH:mm").format("YYYYMMDDHHmm");return`${o(n)}-${o(i)}`}getTSOfSpecificDate(e,{end:t=!1}={}){return Number((0,_lodash.default)(e).thru(e=>(0,_momentTimezone.default)(e.split("(")[0],"YYYY/MM/DD")).thru(e=>t?e.endOf("day"):e.startOf("day")).value().format("YYYYMMDDHHmmss"))}isHttpsURL(e){if(!_lodash.default.isString(e))return!1;try{const t=decodeURIComponent(e.trim());return"https:"===new URL(t).protocol}catch(e){return!1}}generateUniqueCodeMap(e,t=3){if(t<2)throw new Error("代碼長度最少必須為 2。");const r=new Set,a=new Set;return _lodash.default.transform(e,(e,n)=>{if(a.has(n))throw new Error(`23125453 Duplicate key detected: "${n}"`);a.add(n),e[n]=(()=>{const e="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",a=e+"0123456789";let n;do{n=e[Math.floor(52*Math.random())];for(let e=1;e<t;e++)n+=a[Math.floor(62*Math.random())]}while(r.has(n));return r.add(n),n})()},{})}getPriceOfPercentageBehavior(e,t,r=!1){const a=this.toPercentageDecimal(t);return this.getNumberOfMultiplyCeil(e,r?1-a:1+a)}getFeeOfDiscount(e,t){return Math.round(_lodash.default.multiply(e,this.toPercentageDecimal(t)))}mergeArrayByKey(e){if(!Array.isArray(e))return e;const t={};for(const r of e)if(_lodash.default.isPlainObject(r))for(const[e,a]of Object.entries(r))t[e]?_lodash.default.merge(t[e],a):t[e]=_lodash.default.cloneDeep(a);return e.length=0,Object.entries(t).forEach(([t,r])=>{e.push({[t]:r})}),e}getObjectOfStartEndDateTime(e){if(!e||"string"!=typeof e)return{startDate:"",startTime:"",endDate:"",endTime:""};const t=e.replace(/|/g," ").replace(/-/g,"-").replace(/\s+/g," ").trim(),r=/(\d{4}\/\d{1,2}\/\d{1,2})(?:\s*\([^)]*\))?\s*(\d{2}:\d{2})\s*-\s*(\d{4}\/\d{1,2}\/\d{1,2})(?:\s*\([^)]*\))?\s*(\d{2}:\d{2})/,a=/(\d{4}\/\d{1,2}\/\d{1,2})(?:\s*\([^)]*\))?\s*(\d{2}:\d{2})\s*-\s*(\d{2}:\d{2})/;let n,i,o,s;if(r.test(t))[,n,i,o,s]=t.match(r);else{if(!a.test(t))return{startDate:"",startTime:"",endDate:"",endTime:""};[,n,i,s]=t.match(a),o=n}const l=new Date(`${n} ${i}`);if(new Date(`${o} ${s}`)<l)throw new Error(`End time cannot be earlier than start time: ${e}`);return{startDate:n,startTime:i,endDate:o,endTime:s}}getFilteredHeraPeriods(e,t){return _lodash.default.chain(e).filter(e=>e.idOfBooze!==t).uniqBy(e=>`${e.idOfBooze}_${e.idOfVariant}`).value()}checkPeriodConflict(e,t,r=1){const[a,n]=e.content.split("|"),i=(0,_momentTimezone.default)(a.split(" ")[0],"YYYY/MM/DD"),[o,s]=n.split("-"),l=(0,_momentTimezone.default)(`${i.format("YYYY/MM/DD")} ${o}`,"YYYY/MM/DD HH:mm"),u=(0,_momentTimezone.default)(`${i.format("YYYY/MM/DD")} ${s}`,"YYYY/MM/DD HH:mm"),d=_lodash.default.filter(t,e=>{const[t,r]=e.period.split("-"),a=(0,_momentTimezone.default)(t,"YYYYMMDDHHmm"),n=(0,_momentTimezone.default)(r,"YYYYMMDDHHmm");return l.isBefore(n)&&u.isAfter(a)});return{conflict:d.length>=r,items:d}}}var _default=exports.default=Utiller;
|