mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-08 14:34:13 +08:00
数据处理分类新增数组处理、时间处理、字符串处理
This commit is contained in:
parent
e7da2d3a26
commit
44b740de5c
238
plugin/lib/quickcomposer/data/array.js
Normal file
238
plugin/lib/quickcomposer/data/array.js
Normal file
@ -0,0 +1,238 @@
|
||||
const array = {
|
||||
// 安全的条件解析器
|
||||
_parseCondition: function (item, condition) {
|
||||
// 支持的操作符
|
||||
const operators = {
|
||||
"===": (a, b) => a === b,
|
||||
"!==": (a, b) => a !== b,
|
||||
">=": (a, b) => a >= b,
|
||||
"<=": (a, b) => a <= b,
|
||||
">": (a, b) => a > b,
|
||||
"<": (a, b) => a < b,
|
||||
"&&": (a, b) => a && b,
|
||||
"||": (a, b) => a || b,
|
||||
includes: (a, b) => String(a).includes(b),
|
||||
startsWith: (a, b) => String(a).startsWith(b),
|
||||
endsWith: (a, b) => String(a).endsWith(b),
|
||||
};
|
||||
|
||||
try {
|
||||
// 简单属性访问
|
||||
if (/^[a-zA-Z0-9_]+$/.test(condition)) {
|
||||
return item[condition];
|
||||
}
|
||||
|
||||
// 解析复杂条件
|
||||
for (const [op, func] of Object.entries(operators)) {
|
||||
if (condition.includes(op)) {
|
||||
const [left, right] = condition.split(op).map((s) => s.trim());
|
||||
const leftValue = left.includes(".")
|
||||
? left.split(".").reduce((obj, key) => obj[key], item)
|
||||
: /^[a-zA-Z0-9_]+$/.test(left)
|
||||
? item[left]
|
||||
: this._parseValue(left);
|
||||
const rightValue = right.includes(".")
|
||||
? right.split(".").reduce((obj, key) => obj[key], item)
|
||||
: /^[a-zA-Z0-9_]+$/.test(right)
|
||||
? item[right]
|
||||
: this._parseValue(right);
|
||||
return func(leftValue, rightValue);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// 解析值
|
||||
_parseValue: function (value) {
|
||||
if (value === "true") return true;
|
||||
if (value === "false") return false;
|
||||
if (value === "null") return null;
|
||||
if (value === "undefined") return undefined;
|
||||
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
||||
if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
|
||||
if (!isNaN(value)) return Number(value);
|
||||
return value;
|
||||
},
|
||||
|
||||
// 数组过滤
|
||||
filter: function (array, condition) {
|
||||
if (!Array.isArray(array)) return [];
|
||||
return array.filter((item) => this._parseCondition(item, condition));
|
||||
},
|
||||
|
||||
// 查找元素
|
||||
find: function (array, condition) {
|
||||
if (!Array.isArray(array)) return null;
|
||||
return array.find((item) => this._parseCondition(item, condition));
|
||||
},
|
||||
|
||||
// 数组映射
|
||||
map: function (array, transform) {
|
||||
if (!Array.isArray(array)) return [];
|
||||
return array.map((item) => {
|
||||
if (/^[a-zA-Z0-9_.]+$/.test(transform)) {
|
||||
return transform.split(".").reduce((obj, key) => obj?.[key], item);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
},
|
||||
|
||||
// 数组排序
|
||||
sort: function (array, key, order = "asc") {
|
||||
if (!Array.isArray(array)) return [];
|
||||
return [...array].sort((a, b) => {
|
||||
const valueA = key ? a[key] : a;
|
||||
const valueB = key ? b[key] : b;
|
||||
|
||||
if (typeof valueA === "string" && typeof valueB === "string") {
|
||||
return order === "asc"
|
||||
? valueA.localeCompare(valueB)
|
||||
: valueB.localeCompare(valueA);
|
||||
}
|
||||
|
||||
return order === "asc" ? valueA - valueB : valueB - valueA;
|
||||
});
|
||||
},
|
||||
|
||||
// 数组分组
|
||||
group: function (array, key) {
|
||||
if (!Array.isArray(array) || !key) return {};
|
||||
return array.reduce((groups, item) => {
|
||||
const value = key.split(".").reduce((obj, k) => obj?.[k], item);
|
||||
if (value !== undefined) {
|
||||
if (!groups[value]) {
|
||||
groups[value] = [];
|
||||
}
|
||||
groups[value].push(item);
|
||||
}
|
||||
return groups;
|
||||
}, {});
|
||||
},
|
||||
|
||||
// 数组去重
|
||||
unique: function (array, key) {
|
||||
if (!Array.isArray(array)) return [];
|
||||
if (!key) {
|
||||
return [...new Set(array)];
|
||||
}
|
||||
const seen = new Set();
|
||||
return array.filter((item) => {
|
||||
const value = key.split(".").reduce((obj, k) => obj?.[k], item);
|
||||
if (value === undefined || seen.has(value)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(value);
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
// 数组聚合
|
||||
aggregate: function (array, operation, key) {
|
||||
if (!Array.isArray(array)) return null;
|
||||
const values = key
|
||||
? array.map((item) => key.split(".").reduce((obj, k) => obj?.[k], item))
|
||||
: array;
|
||||
const validNumbers = values.filter((v) => !isNaN(v));
|
||||
|
||||
switch (operation) {
|
||||
case "sum":
|
||||
return validNumbers.reduce((sum, val) => sum + val, 0);
|
||||
case "avg":
|
||||
return validNumbers.length
|
||||
? validNumbers.reduce((sum, val) => sum + val, 0) /
|
||||
validNumbers.length
|
||||
: 0;
|
||||
case "max":
|
||||
return validNumbers.length ? Math.max(...validNumbers) : null;
|
||||
case "min":
|
||||
return validNumbers.length ? Math.min(...validNumbers) : null;
|
||||
case "count":
|
||||
return array.length;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// 数组切片
|
||||
slice: function (array, start, end) {
|
||||
if (!Array.isArray(array)) return [];
|
||||
return array.slice(start, end);
|
||||
},
|
||||
|
||||
// 数组扁平化
|
||||
flatten: function (array, depth = 1) {
|
||||
if (!Array.isArray(array)) return [];
|
||||
return array.flat(depth);
|
||||
},
|
||||
|
||||
// 数组差集
|
||||
diff: function (array1, array2, key) {
|
||||
if (!Array.isArray(array1) || !Array.isArray(array2)) return [];
|
||||
if (!key) {
|
||||
return array1.filter((item) => !array2.includes(item));
|
||||
}
|
||||
const set2 = new Set(
|
||||
array2.map((item) => key.split(".").reduce((obj, k) => obj?.[k], item))
|
||||
);
|
||||
return array1.filter(
|
||||
(item) => !set2.has(key.split(".").reduce((obj, k) => obj?.[k], item))
|
||||
);
|
||||
},
|
||||
|
||||
// 数组交集
|
||||
intersect: function (array1, array2, key) {
|
||||
if (!Array.isArray(array1) || !Array.isArray(array2)) return [];
|
||||
if (!key) {
|
||||
return array1.filter((item) => array2.includes(item));
|
||||
}
|
||||
const set2 = new Set(
|
||||
array2.map((item) => key.split(".").reduce((obj, k) => obj?.[k], item))
|
||||
);
|
||||
return array1.filter((item) =>
|
||||
set2.has(key.split(".").reduce((obj, k) => obj?.[k], item))
|
||||
);
|
||||
},
|
||||
|
||||
// 数组并集
|
||||
union: function (array1, array2, key) {
|
||||
if (!Array.isArray(array1) || !Array.isArray(array2)) return [];
|
||||
if (!key) {
|
||||
return [...new Set([...array1, ...array2])];
|
||||
}
|
||||
const seen = new Set();
|
||||
return [...array1, ...array2].filter((item) => {
|
||||
const value = key.split(".").reduce((obj, k) => obj?.[k], item);
|
||||
if (value === undefined || seen.has(value)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(value);
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
// 数组分块
|
||||
chunk: function (array, size = 1) {
|
||||
if (!Array.isArray(array) || size < 1) return [];
|
||||
const chunks = [];
|
||||
for (let i = 0; i < array.length; i += size) {
|
||||
chunks.push(array.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
},
|
||||
|
||||
// 数组随机排序
|
||||
shuffle: function (array) {
|
||||
if (!Array.isArray(array)) return [];
|
||||
const result = [...array];
|
||||
for (let i = result.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[result[i], result[j]] = [result[j], result[i]];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = array;
|
@ -1,11 +1,15 @@
|
||||
const string = require("./string");
|
||||
const buffer = require("./buffer");
|
||||
const zlib = require("./zlib");
|
||||
const htmlParser = require("./htmlParser");
|
||||
const { htmlParser } = require("./htmlParser");
|
||||
const array = require("./array");
|
||||
const time = require("./time");
|
||||
|
||||
module.exports = {
|
||||
...string,
|
||||
...htmlParser,
|
||||
htmlParser,
|
||||
string,
|
||||
buffer,
|
||||
zlib,
|
||||
array,
|
||||
time,
|
||||
};
|
||||
|
@ -1,25 +1,196 @@
|
||||
const string = {
|
||||
// 字符串反转
|
||||
reverseString: function (text) {
|
||||
reverse: function (text) {
|
||||
return text.split("").reverse().join("");
|
||||
},
|
||||
|
||||
// 字符串替换
|
||||
replaceString: function (text, oldStr, newStr) {
|
||||
replace: function (text, oldStr, newStr) {
|
||||
return text.replaceAll(oldStr, newStr);
|
||||
},
|
||||
|
||||
// 字符串截取
|
||||
substring: function (text, start, end) {
|
||||
return text.substring(start, end);
|
||||
},
|
||||
// 正则处理
|
||||
regexTransform: function (text, regex, replace) {
|
||||
try {
|
||||
if (replace === undefined) return text.match(regex);
|
||||
return text.replace(regex, replace);
|
||||
} catch (e) {
|
||||
throw "正则表达式格式错误";
|
||||
|
||||
// 去除空白
|
||||
trim: function (text, mode = "both") {
|
||||
switch (mode) {
|
||||
case "start":
|
||||
return text.trimStart();
|
||||
case "end":
|
||||
return text.trimEnd();
|
||||
default:
|
||||
return text.trim();
|
||||
}
|
||||
},
|
||||
|
||||
// 大小写转换
|
||||
case: function (text, mode = "upper") {
|
||||
switch (mode) {
|
||||
case "upper":
|
||||
return text.toUpperCase();
|
||||
case "lower":
|
||||
return text.toLowerCase();
|
||||
case "capitalize":
|
||||
return text
|
||||
.toLowerCase()
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
case "camel":
|
||||
return text
|
||||
.toLowerCase()
|
||||
.split(/[^a-zA-Z0-9]+/)
|
||||
.map((word, index) =>
|
||||
index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
|
||||
)
|
||||
.join("");
|
||||
case "snake":
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^a-zA-Z0-9]+/g, "_")
|
||||
.replace(/([A-Z])/g, "_$1")
|
||||
.replace(/^_/, "");
|
||||
case "kebab":
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^a-zA-Z0-9]+/g, "-")
|
||||
.replace(/([A-Z])/g, "-$1")
|
||||
.replace(/^-/, "");
|
||||
case "constant":
|
||||
return text
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z0-9]+/g, "_")
|
||||
.replace(/^_/, "");
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
},
|
||||
|
||||
// 字符串填充
|
||||
pad: function (text, length, padString = " ", mode = "end") {
|
||||
const totalPadding = Math.max(0, length - text.length);
|
||||
switch (mode) {
|
||||
case "start":
|
||||
return text.padStart(length, padString);
|
||||
case "end":
|
||||
return text.padEnd(length, padString);
|
||||
case "both":
|
||||
const leftPadding = Math.floor(totalPadding / 2);
|
||||
const rightPadding = totalPadding - leftPadding;
|
||||
return text
|
||||
.padStart(text.length + leftPadding, padString)
|
||||
.padEnd(length, padString);
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
},
|
||||
|
||||
// 字符串分割
|
||||
split: function (text, separator = ",") {
|
||||
return text.split(separator);
|
||||
},
|
||||
|
||||
// 数组合并
|
||||
join: function (array, separator = ",") {
|
||||
return Array.isArray(array) ? array.join(separator) : String(array);
|
||||
},
|
||||
|
||||
// 字符串重复
|
||||
repeat: function (text, count = 1) {
|
||||
return text.repeat(Math.max(0, count));
|
||||
},
|
||||
|
||||
// 提取字符
|
||||
extract: function (text, mode = "number") {
|
||||
const patterns = {
|
||||
number: /\d+/g,
|
||||
letter: /[a-zA-Z]+/g,
|
||||
chinese: /[\u4e00-\u9fa5]+/g,
|
||||
punctuation: /[^\w\s\u4e00-\u9fa5]+/g,
|
||||
whitespace: /\s+/g,
|
||||
};
|
||||
const matches = text.match(patterns[mode] || patterns.number);
|
||||
return matches ? matches : [];
|
||||
},
|
||||
|
||||
// 字符统计
|
||||
count: function (text, mode = "char") {
|
||||
switch (mode) {
|
||||
case "char":
|
||||
return text.length;
|
||||
case "word":
|
||||
return text.trim().split(/\s+/).length;
|
||||
case "line":
|
||||
return text.split(/\r\n|\r|\n/).length;
|
||||
case "number":
|
||||
return (text.match(/\d/g) || []).length;
|
||||
case "letter":
|
||||
return (text.match(/[a-zA-Z]/g) || []).length;
|
||||
case "chinese":
|
||||
return (text.match(/[\u4e00-\u9fa5]/g) || []).length;
|
||||
case "whitespace":
|
||||
return (text.match(/\s/g) || []).length;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
// 文本换行
|
||||
wrap: function (text, width = 80) {
|
||||
const words = text.split(/(\s+)/);
|
||||
let line = "";
|
||||
let result = "";
|
||||
|
||||
for (const word of words) {
|
||||
if (line.length + word.length > width) {
|
||||
result += line.trimEnd() + "\n";
|
||||
line = "";
|
||||
}
|
||||
line += word;
|
||||
}
|
||||
|
||||
return result + line.trimEnd();
|
||||
},
|
||||
|
||||
// 文本对齐
|
||||
align: function (text, mode = "left", width = 80) {
|
||||
const lines = text.split("\n");
|
||||
return lines
|
||||
.map((line) => {
|
||||
const spaces = width - line.length;
|
||||
if (spaces <= 0) return line;
|
||||
|
||||
switch (mode) {
|
||||
case "right":
|
||||
return " ".repeat(spaces) + line;
|
||||
case "center":
|
||||
const leftSpaces = Math.floor(spaces / 2);
|
||||
return (
|
||||
" ".repeat(leftSpaces) + line + " ".repeat(spaces - leftSpaces)
|
||||
);
|
||||
case "justify":
|
||||
if (line.trim() === "") return line;
|
||||
const words = line.trim().split(/\s+/);
|
||||
if (words.length === 1) return line;
|
||||
const totalSpaces = width - words.join("").length;
|
||||
const spaceBetween = Math.floor(totalSpaces / (words.length - 1));
|
||||
const extraSpaces = totalSpaces % (words.length - 1);
|
||||
return words
|
||||
.map((word, i) =>
|
||||
i < words.length - 1
|
||||
? word + " ".repeat(spaceBetween + (i < extraSpaces ? 1 : 0))
|
||||
: word
|
||||
)
|
||||
.join("");
|
||||
default:
|
||||
return line;
|
||||
}
|
||||
})
|
||||
.join("\n");
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = string;
|
||||
|
350
plugin/lib/quickcomposer/data/time.js
Normal file
350
plugin/lib/quickcomposer/data/time.js
Normal file
@ -0,0 +1,350 @@
|
||||
const time = {
|
||||
// 解析时间字符串或时间戳为 Date 对象
|
||||
_parseTime: function (time) {
|
||||
if (!time || time === "now") {
|
||||
return new Date();
|
||||
}
|
||||
if (typeof time === "number") {
|
||||
return new Date(time.toString().length === 10 ? time * 1000 : time);
|
||||
}
|
||||
return new Date(time);
|
||||
},
|
||||
|
||||
// 格式化数字为两位数
|
||||
_pad: function (number) {
|
||||
return number.toString().padStart(2, "0");
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
format: function (time, format) {
|
||||
const date = this._parseTime(time);
|
||||
if (!date.getTime()) return "";
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = this._pad(date.getMonth() + 1);
|
||||
const day = this._pad(date.getDate());
|
||||
const hours = this._pad(date.getHours());
|
||||
const minutes = this._pad(date.getMinutes());
|
||||
const seconds = this._pad(date.getSeconds());
|
||||
|
||||
switch (format) {
|
||||
case "YYYY-MM-DD":
|
||||
return `${year}-${month}-${day}`;
|
||||
case "YYYY-MM-DD HH:mm:ss":
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
case "YYYY年MM月DD日":
|
||||
return `${year}年${month}月${day}日`;
|
||||
case "MM/DD/YYYY":
|
||||
return `${month}/${day}/${year}`;
|
||||
case "DD/MM/YYYY":
|
||||
return `${day}/${month}/${year}`;
|
||||
case "HH:mm:ss":
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
case "YYYY-MM-DD HH:mm":
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
case "timestamp":
|
||||
return Math.floor(date.getTime() / 1000);
|
||||
case "timestamp_ms":
|
||||
return date.getTime();
|
||||
case "relative":
|
||||
return this._getRelativeTime(date);
|
||||
default:
|
||||
return date.toLocaleString();
|
||||
}
|
||||
},
|
||||
|
||||
// 获取相对时间描述
|
||||
_getRelativeTime: function (date) {
|
||||
const now = new Date();
|
||||
const diff = now - date;
|
||||
const seconds = Math.floor(diff / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
const months = Math.floor(days / 30);
|
||||
const years = Math.floor(days / 365);
|
||||
|
||||
if (seconds < 60) return "刚刚";
|
||||
if (minutes < 60) return `${minutes}分钟前`;
|
||||
if (hours < 24) return `${hours}小时前`;
|
||||
if (days < 30) return `${days}天前`;
|
||||
if (months < 12) return `${months}个月前`;
|
||||
return `${years}年前`;
|
||||
},
|
||||
|
||||
// 解析时间
|
||||
parse: function (time, format) {
|
||||
if (!time) return null;
|
||||
|
||||
// 处理时间戳
|
||||
if (format === "timestamp") {
|
||||
return new Date(Number(time) * 1000);
|
||||
}
|
||||
if (format === "timestamp_ms") {
|
||||
return new Date(Number(time));
|
||||
}
|
||||
|
||||
// 处理标准格式
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
let result;
|
||||
|
||||
switch (format) {
|
||||
case "YYYY-MM-DD":
|
||||
case "YYYY-MM-DD HH:mm:ss":
|
||||
case "YYYY-MM-DD HH:mm":
|
||||
result = new Date(time);
|
||||
break;
|
||||
case "YYYY年MM月DD日":
|
||||
time = time.replace(/[年月日]/g, (match) => {
|
||||
return { 年: "-", 月: "-", 日: "" }[match];
|
||||
});
|
||||
result = new Date(time);
|
||||
break;
|
||||
case "MM/DD/YYYY":
|
||||
const [month, day, yyyy] = time.split("/");
|
||||
result = new Date(yyyy, month - 1, day);
|
||||
break;
|
||||
case "DD/MM/YYYY":
|
||||
const [dd, mm, yy] = time.split("/");
|
||||
result = new Date(yy, mm - 1, dd);
|
||||
break;
|
||||
default:
|
||||
result = new Date(time);
|
||||
}
|
||||
|
||||
return result.getTime() ? result : null;
|
||||
},
|
||||
|
||||
// 时间加减
|
||||
add: function (time, value, unit) {
|
||||
const date = this._parseTime(time);
|
||||
if (!date.getTime()) return null;
|
||||
|
||||
const unitMap = {
|
||||
years: "FullYear",
|
||||
months: "Month",
|
||||
weeks: "Date",
|
||||
days: "Date",
|
||||
hours: "Hours",
|
||||
minutes: "Minutes",
|
||||
seconds: "Seconds",
|
||||
};
|
||||
|
||||
const methodName = `set${unitMap[unit]}`;
|
||||
const getValue = `get${unitMap[unit]}`;
|
||||
|
||||
if (unit === "weeks") {
|
||||
value *= 7;
|
||||
}
|
||||
|
||||
date[methodName](date[getValue]() + value);
|
||||
return date;
|
||||
},
|
||||
|
||||
// 时间差值
|
||||
diff: function (time1, time2, unit = "days", absolute = true) {
|
||||
const date1 = this._parseTime(time1);
|
||||
const date2 = this._parseTime(time2);
|
||||
|
||||
if (!date1.getTime() || !date2.getTime()) return null;
|
||||
|
||||
const milliseconds = date2 - date1;
|
||||
let result;
|
||||
|
||||
switch (unit) {
|
||||
case "years":
|
||||
result = date2.getFullYear() - date1.getFullYear();
|
||||
break;
|
||||
case "months":
|
||||
result =
|
||||
(date2.getFullYear() - date1.getFullYear()) * 12 +
|
||||
(date2.getMonth() - date1.getMonth());
|
||||
break;
|
||||
case "weeks":
|
||||
result = milliseconds / (7 * 24 * 60 * 60 * 1000);
|
||||
break;
|
||||
case "days":
|
||||
result = milliseconds / (24 * 60 * 60 * 1000);
|
||||
break;
|
||||
case "hours":
|
||||
result = milliseconds / (60 * 60 * 1000);
|
||||
break;
|
||||
case "minutes":
|
||||
result = milliseconds / (60 * 1000);
|
||||
break;
|
||||
case "seconds":
|
||||
result = milliseconds / 1000;
|
||||
break;
|
||||
case "milliseconds":
|
||||
result = milliseconds;
|
||||
break;
|
||||
default:
|
||||
result = milliseconds / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
return absolute ? Math.abs(result) : result;
|
||||
},
|
||||
|
||||
// 时间边界
|
||||
startOf: function (time, unit, type = "start") {
|
||||
const date = this._parseTime(time);
|
||||
if (!date.getTime()) return null;
|
||||
|
||||
const isEnd = type === "end";
|
||||
|
||||
switch (unit) {
|
||||
case "year":
|
||||
date.setMonth(isEnd ? 11 : 0, 1);
|
||||
date.setHours(
|
||||
isEnd ? 23 : 0,
|
||||
isEnd ? 59 : 0,
|
||||
isEnd ? 59 : 0,
|
||||
isEnd ? 999 : 0
|
||||
);
|
||||
break;
|
||||
case "month":
|
||||
date.setDate(1);
|
||||
if (isEnd) {
|
||||
date.setMonth(date.getMonth() + 1, 0);
|
||||
date.setHours(23, 59, 59, 999);
|
||||
} else {
|
||||
date.setHours(0, 0, 0, 0);
|
||||
}
|
||||
break;
|
||||
case "week":
|
||||
const day = date.getDay();
|
||||
const diff = date.getDate() - day + (day === 0 ? -6 : 1);
|
||||
date.setDate(diff);
|
||||
if (isEnd) {
|
||||
date.setDate(date.getDate() + 6);
|
||||
date.setHours(23, 59, 59, 999);
|
||||
} else {
|
||||
date.setHours(0, 0, 0, 0);
|
||||
}
|
||||
break;
|
||||
case "day":
|
||||
date.setHours(
|
||||
isEnd ? 23 : 0,
|
||||
isEnd ? 59 : 0,
|
||||
isEnd ? 59 : 0,
|
||||
isEnd ? 999 : 0
|
||||
);
|
||||
break;
|
||||
case "hour":
|
||||
date.setMinutes(isEnd ? 59 : 0, isEnd ? 59 : 0, isEnd ? 999 : 0);
|
||||
break;
|
||||
case "minute":
|
||||
date.setSeconds(isEnd ? 59 : 0, isEnd ? 999 : 0);
|
||||
break;
|
||||
case "second":
|
||||
date.setMilliseconds(isEnd ? 999 : 0);
|
||||
break;
|
||||
}
|
||||
|
||||
return date;
|
||||
},
|
||||
|
||||
// 时间验证
|
||||
isValid: function (time, format) {
|
||||
if (!time) return false;
|
||||
const date = this.parse(time, format);
|
||||
return date && date.getTime() > 0;
|
||||
},
|
||||
|
||||
// 日历信息
|
||||
calendar: function (time) {
|
||||
const date = this._parseTime(time);
|
||||
if (!date.getTime()) return null;
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const week = date.getDay();
|
||||
const weekText = ["日", "一", "二", "三", "四", "五", "六"][week];
|
||||
|
||||
// 星座计算
|
||||
const constellation = this._getConstellation(month, day);
|
||||
|
||||
return {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
week,
|
||||
weekText: `星期${weekText}`,
|
||||
constellation,
|
||||
isWeekend: week === 0 || week === 6,
|
||||
isLeapYear: (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0,
|
||||
daysInMonth: new Date(year, month, 0).getDate(),
|
||||
timestamp: Math.floor(date.getTime() / 1000),
|
||||
timestamp_ms: date.getTime(),
|
||||
};
|
||||
},
|
||||
|
||||
// 获取星座
|
||||
_getConstellation: function (month, day) {
|
||||
const constellations = [
|
||||
"魔羯",
|
||||
"水瓶",
|
||||
"双鱼",
|
||||
"白羊",
|
||||
"金牛",
|
||||
"双子",
|
||||
"巨蟹",
|
||||
"狮子",
|
||||
"处女",
|
||||
"天秤",
|
||||
"天蝎",
|
||||
"射手",
|
||||
"魔羯",
|
||||
];
|
||||
const dates = [20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 23, 22];
|
||||
return day < dates[month - 1]
|
||||
? constellations[month - 1]
|
||||
: constellations[month];
|
||||
},
|
||||
|
||||
// 工作日计算
|
||||
workday: function (startTime, days, weekends = false, holidays = []) {
|
||||
const date = this._parseTime(startTime);
|
||||
if (!date.getTime()) return null;
|
||||
|
||||
let currentDate = new Date(date);
|
||||
let remainingDays = Math.abs(days);
|
||||
const direction = days >= 0 ? 1 : -1;
|
||||
const holidaySet = new Set(
|
||||
holidays.map((h) => this.format(h, "YYYY-MM-DD"))
|
||||
);
|
||||
|
||||
while (remainingDays > 0) {
|
||||
currentDate.setDate(currentDate.getDate() + direction);
|
||||
const isWeekend =
|
||||
currentDate.getDay() === 0 || currentDate.getDay() === 6;
|
||||
const isHoliday = holidaySet.has(this.format(currentDate, "YYYY-MM-DD"));
|
||||
|
||||
if ((!isWeekend || weekends) && !isHoliday) {
|
||||
remainingDays--;
|
||||
}
|
||||
}
|
||||
|
||||
return currentDate;
|
||||
},
|
||||
|
||||
// 时间范围判断
|
||||
between: function (time, startTime, endTime, inclusive = true) {
|
||||
const date = this._parseTime(time);
|
||||
const start = this._parseTime(startTime);
|
||||
const end = this._parseTime(endTime);
|
||||
|
||||
if (!date.getTime() || !start.getTime() || !end.getTime()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inclusive) {
|
||||
return date >= start && date <= end;
|
||||
}
|
||||
return date > start && date < end;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = time;
|
@ -96,7 +96,18 @@ export default defineComponent({
|
||||
},
|
||||
saveFlow() {
|
||||
const flow = window.lodashM.cloneDeep(this.commandFlow);
|
||||
const uselessProps = ["config", "argvs", "label", "component", "subCommands", "options", "defaultValue"];
|
||||
const uselessProps = [
|
||||
"config",
|
||||
"argvs",
|
||||
"label",
|
||||
"component",
|
||||
"subCommands",
|
||||
"options",
|
||||
"defaultValue",
|
||||
"icon",
|
||||
"width",
|
||||
"placeholder",
|
||||
];
|
||||
// 移除不必要属性
|
||||
flow.forEach((cmd) => {
|
||||
for (const props of uselessProps) {
|
||||
|
@ -6,15 +6,16 @@
|
||||
:model-value="isCollapse"
|
||||
>
|
||||
<div class="array-editor">
|
||||
<div v-for="(row, index) in rows" :key="index" class="row items-center">
|
||||
<template v-if="columns">
|
||||
<div
|
||||
v-for="(row, index) in rows"
|
||||
:key="index"
|
||||
class="row items-center q-gutter-sm"
|
||||
>
|
||||
<template v-if="!!columns">
|
||||
<div
|
||||
v-for="column in processedColumns"
|
||||
:key="column.key"
|
||||
:class="[
|
||||
column.width ? `col-${column.width}` : 'col',
|
||||
Object.keys(columns).length > 1 ? 'q-pr-sm' : '',
|
||||
]"
|
||||
:class="[column.width ? `col-${column.width}` : 'col']"
|
||||
>
|
||||
<VariableInput
|
||||
:model-value="row[column.key]"
|
||||
|
145
src/components/composer/common/CheckButton.vue
Normal file
145
src/components/composer/common/CheckButton.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="check-btn-group">
|
||||
<q-btn
|
||||
:color="modelValue ? 'primary' : 'grey-7'"
|
||||
:flat="!modelValue"
|
||||
:outline="modelValue"
|
||||
dense
|
||||
:class="['check-btn', { 'check-btn--selected': modelValue }]"
|
||||
@click="toggleValue"
|
||||
>
|
||||
<template #default>
|
||||
<div class="row items-center full-width">
|
||||
<div class="check-btn-content">
|
||||
<div class="check-btn-label">{{ label }}</div>
|
||||
</div>
|
||||
<q-icon
|
||||
:name="modelValue ? 'check_circle' : 'radio_button_unchecked'"
|
||||
size="14px"
|
||||
class="q-ml-xs check-btn-icon"
|
||||
/>
|
||||
</div>
|
||||
<q-tooltip v-if="tooltip">{{ tooltip }}</q-tooltip>
|
||||
</template>
|
||||
</q-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "CheckButton",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
isCollapse: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
methods: {
|
||||
toggleValue() {
|
||||
this.$emit("update:model-value", !this.modelValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.check-btn-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.check-btn {
|
||||
min-width: 100%;
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
min-height: 36px;
|
||||
font-size: 12px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px !important;
|
||||
transition: all 0.3s;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.check-btn :deep(.q-btn__content) {
|
||||
min-width: 0;
|
||||
height: auto;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.check-btn-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.check-btn-label {
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
word-break: break-word;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.check-btn-icon {
|
||||
flex: none;
|
||||
opacity: 0.8;
|
||||
transition: all 0.3s;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.check-btn--selected .check-btn-icon {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.check-btn:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-1px);
|
||||
border-color: var(--q-primary);
|
||||
}
|
||||
|
||||
.body--dark .check-btn {
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.check-btn--selected {
|
||||
background-color: transparent !important;
|
||||
border-color: var(--q-primary) !important;
|
||||
}
|
||||
|
||||
.check-btn.q-btn--flat {
|
||||
color: var(--q-primary);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.body--dark .check-btn.q-btn--flat {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.check-btn.q-btn--outline {
|
||||
opacity: 1;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
@ -4,6 +4,7 @@
|
||||
<div class="cards-wrapper">
|
||||
<div
|
||||
v-for="option in options"
|
||||
ref="operationCard"
|
||||
:key="option.value"
|
||||
:class="['operation-card', { active: modelValue === option.value }]"
|
||||
:data-value="option.value"
|
||||
@ -71,14 +72,19 @@ export default {
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(newVal) {
|
||||
document
|
||||
.querySelector(`.operation-card[data-value="${newVal}"]`)
|
||||
?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "nearest",
|
||||
modelValue: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.operationCard
|
||||
?.find((card) => card.dataset.value === newVal)
|
||||
?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "nearest",
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ import DictEditor from "./DictEditor.vue";
|
||||
import ButtonGroup from "./ButtonGroup.vue";
|
||||
import ControlInput from "./ControlInput.vue";
|
||||
import CheckGroup from "./CheckGroup.vue";
|
||||
|
||||
import CheckButton from "./CheckButton.vue";
|
||||
export default defineComponent({
|
||||
name: "OptionEditor",
|
||||
components: {
|
||||
@ -53,6 +53,7 @@ export default defineComponent({
|
||||
ButtonGroup,
|
||||
ControlInput,
|
||||
CheckGroup,
|
||||
CheckButton,
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
props: {
|
||||
|
@ -34,6 +34,7 @@ import ButtonGroup from "./ButtonGroup.vue";
|
||||
import ControlInput from "./ControlInput.vue";
|
||||
import CheckGroup from "./CheckGroup.vue";
|
||||
import OptionEditor from "./OptionEditor.vue";
|
||||
import CheckButton from "./CheckButton.vue";
|
||||
|
||||
/**
|
||||
* 参数输入组件
|
||||
@ -57,6 +58,7 @@ export default defineComponent({
|
||||
ControlInput,
|
||||
CheckGroup,
|
||||
OptionEditor,
|
||||
CheckButton,
|
||||
},
|
||||
props: {
|
||||
configs: {
|
||||
|
@ -223,13 +223,12 @@ export default defineComponent({
|
||||
},
|
||||
title: {
|
||||
label: "标题",
|
||||
width: 4,
|
||||
noIcon: true,
|
||||
},
|
||||
description: {
|
||||
label: "描述",
|
||||
width: 4,
|
||||
noIcon: true,
|
||||
width: 4,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -259,7 +259,7 @@ export const networkCommands = {
|
||||
},
|
||||
{
|
||||
label: "返回所有地址",
|
||||
component: "q-checkbox",
|
||||
component: "CheckButton",
|
||||
defaultValue: false,
|
||||
width: 2.5,
|
||||
},
|
||||
|
@ -270,7 +270,7 @@ export const systemCommands = {
|
||||
},
|
||||
{
|
||||
label: "包含内部接口",
|
||||
component: "q-checkbox",
|
||||
component: "CheckButton",
|
||||
defaultValue: false,
|
||||
width: 12,
|
||||
condition: "values[0] === 'networkInterfaces'",
|
||||
|
@ -115,6 +115,7 @@ export const uiCommands = {
|
||||
isAsync: true,
|
||||
outputVariable: "{id,text}",
|
||||
saveOutput: true,
|
||||
width: 12,
|
||||
config: [
|
||||
{
|
||||
label: "按钮",
|
||||
@ -133,6 +134,7 @@ export const uiCommands = {
|
||||
isAsync: true,
|
||||
outputVariable: "[inputValue1]",
|
||||
saveOutput: true,
|
||||
width: 12,
|
||||
config: [
|
||||
{
|
||||
label: "输入框",
|
||||
|
@ -40,7 +40,7 @@ export const userdataCommands = {
|
||||
},
|
||||
{
|
||||
label: "不同步",
|
||||
component: "q-checkbox",
|
||||
component: "CheckButton",
|
||||
defaultValue: true,
|
||||
width: 2,
|
||||
},
|
||||
|
@ -113,32 +113,32 @@ export const utoolsCommands = {
|
||||
forward: {
|
||||
label: "向前查找",
|
||||
icon: "arrow_right",
|
||||
width: 2,
|
||||
component: "q-checkbox",
|
||||
width: 2.4,
|
||||
component: "CheckButton",
|
||||
},
|
||||
findNext: {
|
||||
label: "查找下一个",
|
||||
icon: "arrow_down",
|
||||
width: 2,
|
||||
component: "q-checkbox",
|
||||
width: 2.4,
|
||||
component: "CheckButton",
|
||||
},
|
||||
matchCase: {
|
||||
label: "区分大小写",
|
||||
icon: "arrow_up",
|
||||
width: 2,
|
||||
component: "q-checkbox",
|
||||
width: 2.4,
|
||||
component: "CheckButton",
|
||||
},
|
||||
wordStart: {
|
||||
label: "单词开头",
|
||||
icon: "arrow_right",
|
||||
width: 2,
|
||||
component: "q-checkbox",
|
||||
width: 2.4,
|
||||
component: "CheckButton",
|
||||
},
|
||||
medialCapitalAsWordStart: {
|
||||
label: "中缀大写作为单词开头",
|
||||
label: "中缀大写开头",
|
||||
icon: "arrow_right",
|
||||
width: 4,
|
||||
component: "q-checkbox",
|
||||
width: 2.4,
|
||||
component: "CheckButton",
|
||||
},
|
||||
},
|
||||
defaultValue: {
|
||||
|
@ -16,7 +16,11 @@ export const availableCommands = categories.reduce((commands, category) => {
|
||||
}, []);
|
||||
|
||||
export const findCommandByValue = (value) => {
|
||||
return availableCommands.find((cmd) => cmd.value === value);
|
||||
return availableCommands.find(
|
||||
(cmd) =>
|
||||
cmd.value === value ||
|
||||
cmd.subCommands?.find((subCmd) => subCmd.value === value)
|
||||
);
|
||||
};
|
||||
|
||||
export const commandCategories = categories;
|
||||
|
Loading…
x
Reference in New Issue
Block a user