mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-07-02 05:52:46 +08:00
编排新增url/dns/buffer/zlib,新增arrayEditor组件,优化parseFunction
This commit is contained in:
parent
54bb43dcc8
commit
a6cc1c8737
@ -3,6 +3,8 @@ const quickcomposer = {
|
|||||||
simulate: require("./quickcomposer/simulate"),
|
simulate: require("./quickcomposer/simulate"),
|
||||||
file: require("./quickcomposer/file"),
|
file: require("./quickcomposer/file"),
|
||||||
system: require("./quickcomposer/system"),
|
system: require("./quickcomposer/system"),
|
||||||
|
network: require("./quickcomposer/network"),
|
||||||
|
developer: require("./quickcomposer/developer"),
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = quickcomposer;
|
module.exports = quickcomposer;
|
||||||
|
140
plugin/lib/quickcomposer/developer/buffer.js
Normal file
140
plugin/lib/quickcomposer/developer/buffer.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// 创建 Buffer
|
||||||
|
function from(data, encoding = "utf8") {
|
||||||
|
try {
|
||||||
|
return Buffer.from(data, encoding);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`创建Buffer失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为字符串
|
||||||
|
function toString(buffer, encoding = "utf8", start = 0, end = buffer.length) {
|
||||||
|
try {
|
||||||
|
return buffer.toString(encoding, start, end);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`转换字符串失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入数据
|
||||||
|
function write(
|
||||||
|
buffer,
|
||||||
|
string,
|
||||||
|
offset = 0,
|
||||||
|
length = buffer.length,
|
||||||
|
encoding = "utf8"
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return buffer.write(string, offset, length, encoding);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`写入数据失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充数据
|
||||||
|
function fill(
|
||||||
|
buffer,
|
||||||
|
value,
|
||||||
|
offset = 0,
|
||||||
|
end = buffer.length,
|
||||||
|
encoding = "utf8"
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return buffer.fill(value, offset, end, encoding);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`填充数据失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制数据
|
||||||
|
function copy(
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
targetStart = 0,
|
||||||
|
sourceStart = 0,
|
||||||
|
sourceEnd = source.length
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return source.copy(target, targetStart, sourceStart, sourceEnd);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`复制数据失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较数据
|
||||||
|
function compare(buf1, buf2) {
|
||||||
|
try {
|
||||||
|
return Buffer.compare(buf1, buf2);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`比较数据失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接 Buffer
|
||||||
|
function concat(buffers, totalLength) {
|
||||||
|
try {
|
||||||
|
return Buffer.concat(buffers, totalLength);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`连接Buffer失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找数据
|
||||||
|
function indexOf(buffer, value, byteOffset = 0, encoding = "utf8") {
|
||||||
|
try {
|
||||||
|
return buffer.indexOf(value, byteOffset, encoding);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`查找数据失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切片数据
|
||||||
|
function slice(buffer, start = 0, end = buffer.length) {
|
||||||
|
try {
|
||||||
|
return buffer.slice(start, end);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`切片数据失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 交换字节序
|
||||||
|
function swap(buffer, size) {
|
||||||
|
try {
|
||||||
|
switch (size) {
|
||||||
|
case 16:
|
||||||
|
return buffer.swap16();
|
||||||
|
case 32:
|
||||||
|
return buffer.swap32();
|
||||||
|
case 64:
|
||||||
|
return buffer.swap64();
|
||||||
|
default:
|
||||||
|
throw new Error("不支持的字节大小");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`交换字节序失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
from,
|
||||||
|
toString,
|
||||||
|
write,
|
||||||
|
fill,
|
||||||
|
copy,
|
||||||
|
compare,
|
||||||
|
concat,
|
||||||
|
indexOf,
|
||||||
|
slice,
|
||||||
|
swap,
|
||||||
|
// 编码类型
|
||||||
|
encodings: [
|
||||||
|
"utf8",
|
||||||
|
"utf16le",
|
||||||
|
"latin1",
|
||||||
|
"base64",
|
||||||
|
"hex",
|
||||||
|
"ascii",
|
||||||
|
"binary",
|
||||||
|
"ucs2",
|
||||||
|
],
|
||||||
|
};
|
3
plugin/lib/quickcomposer/developer/index.js
Normal file
3
plugin/lib/quickcomposer/developer/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
buffer: require("./buffer"),
|
||||||
|
};
|
@ -1,5 +1,7 @@
|
|||||||
const operation = require("./operation");
|
const operation = require("./operation");
|
||||||
|
const zlib = require("./zlib");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
operation: operation.operation,
|
operation: operation.operation,
|
||||||
|
zlib: zlib,
|
||||||
};
|
};
|
||||||
|
75
plugin/lib/quickcomposer/file/zlib.js
Normal file
75
plugin/lib/quickcomposer/file/zlib.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const zlib = require("zlib");
|
||||||
|
const { promisify } = require("util");
|
||||||
|
|
||||||
|
// 压缩方法
|
||||||
|
const gzip = promisify(zlib.gzip);
|
||||||
|
const deflate = promisify(zlib.deflate);
|
||||||
|
const brotliCompress = promisify(zlib.brotliCompress);
|
||||||
|
|
||||||
|
// 解压方法
|
||||||
|
const gunzip = promisify(zlib.gunzip);
|
||||||
|
const inflate = promisify(zlib.inflate);
|
||||||
|
const brotliDecompress = promisify(zlib.brotliDecompress);
|
||||||
|
|
||||||
|
// 压缩选项
|
||||||
|
const defaultGzipOptions = {
|
||||||
|
level: zlib.constants.Z_DEFAULT_COMPRESSION,
|
||||||
|
memLevel: zlib.constants.Z_DEFAULT_MEMLEVEL,
|
||||||
|
strategy: zlib.constants.Z_DEFAULT_STRATEGY,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultBrotliOptions = {
|
||||||
|
params: {
|
||||||
|
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_GENERIC,
|
||||||
|
[zlib.constants.BROTLI_PARAM_QUALITY]:
|
||||||
|
zlib.constants.BROTLI_DEFAULT_QUALITY,
|
||||||
|
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 异步压缩函数
|
||||||
|
async function compressData(data, method, options = {}) {
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
switch (method) {
|
||||||
|
case "gzip":
|
||||||
|
return await gzip(buffer, { ...defaultGzipOptions, ...options });
|
||||||
|
case "deflate":
|
||||||
|
return await deflate(buffer, { ...defaultGzipOptions, ...options });
|
||||||
|
case "brotli":
|
||||||
|
return await brotliCompress(buffer, {
|
||||||
|
...defaultBrotliOptions,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
throw new Error("不支持的压缩方法");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`压缩失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步解压函数
|
||||||
|
async function decompressData(data, method, options = {}) {
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
switch (method) {
|
||||||
|
case "gzip":
|
||||||
|
return await gunzip(buffer, options);
|
||||||
|
case "deflate":
|
||||||
|
return await inflate(buffer, options);
|
||||||
|
case "brotli":
|
||||||
|
return await brotliDecompress(buffer, options);
|
||||||
|
default:
|
||||||
|
throw new Error("不支持的解压方法");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`解压失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
compressData,
|
||||||
|
decompressData,
|
||||||
|
constants: zlib.constants,
|
||||||
|
};
|
106
plugin/lib/quickcomposer/network/dns.js
Normal file
106
plugin/lib/quickcomposer/network/dns.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
const dns = require("dns");
|
||||||
|
const { promisify } = require("util");
|
||||||
|
|
||||||
|
// 将回调函数转换为 Promise
|
||||||
|
const lookup = promisify(dns.lookup);
|
||||||
|
const resolve = promisify(dns.resolve);
|
||||||
|
const resolve4 = promisify(dns.resolve4);
|
||||||
|
const resolve6 = promisify(dns.resolve6);
|
||||||
|
const resolveMx = promisify(dns.resolveMx);
|
||||||
|
const resolveTxt = promisify(dns.resolveTxt);
|
||||||
|
const resolveNs = promisify(dns.resolveNs);
|
||||||
|
const resolveCname = promisify(dns.resolveCname);
|
||||||
|
const reverse = promisify(dns.reverse);
|
||||||
|
|
||||||
|
// 解析主机名
|
||||||
|
async function lookupHost(hostname, options = {}) {
|
||||||
|
try {
|
||||||
|
return await lookup(hostname, options);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`DNS查询失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析所有记录
|
||||||
|
async function resolveAll(hostname) {
|
||||||
|
try {
|
||||||
|
return await resolve(hostname);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`DNS解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 IPv4 地址
|
||||||
|
async function resolveIpv4(hostname) {
|
||||||
|
try {
|
||||||
|
return await resolve4(hostname);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`IPv4解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 IPv6 地址
|
||||||
|
async function resolveIpv6(hostname) {
|
||||||
|
try {
|
||||||
|
return await resolve6(hostname);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`IPv6解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 MX 记录
|
||||||
|
async function resolveMxRecords(hostname) {
|
||||||
|
try {
|
||||||
|
return await resolveMx(hostname);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`MX记录解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 TXT 记录
|
||||||
|
async function resolveTxtRecords(hostname) {
|
||||||
|
try {
|
||||||
|
return await resolveTxt(hostname);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`TXT记录解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 NS 记录
|
||||||
|
async function resolveNsRecords(hostname) {
|
||||||
|
try {
|
||||||
|
return await resolveNs(hostname);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`NS记录解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 CNAME 记录
|
||||||
|
async function resolveCnameRecords(hostname) {
|
||||||
|
try {
|
||||||
|
return await resolveCname(hostname);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`CNAME记录解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反向解析 IP 地址
|
||||||
|
async function reverseResolve(ip) {
|
||||||
|
try {
|
||||||
|
return await reverse(ip);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`反向解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
lookupHost,
|
||||||
|
resolveAll,
|
||||||
|
resolveIpv4,
|
||||||
|
resolveIpv6,
|
||||||
|
resolveMxRecords,
|
||||||
|
resolveTxtRecords,
|
||||||
|
resolveNsRecords,
|
||||||
|
resolveCnameRecords,
|
||||||
|
reverseResolve,
|
||||||
|
};
|
4
plugin/lib/quickcomposer/network/index.js
Normal file
4
plugin/lib/quickcomposer/network/index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
url: require("./url"),
|
||||||
|
dns: require("./dns"),
|
||||||
|
};
|
98
plugin/lib/quickcomposer/network/url.js
Normal file
98
plugin/lib/quickcomposer/network/url.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
const url = require("url");
|
||||||
|
|
||||||
|
// URL 解析
|
||||||
|
function parse(urlString, parseQueryString = false) {
|
||||||
|
return url.parse(urlString, parseQueryString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL 格式化
|
||||||
|
function format(urlObject) {
|
||||||
|
return url.format(urlObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析查询字符串
|
||||||
|
function parseQuery(query) {
|
||||||
|
const searchParams = new URLSearchParams(query);
|
||||||
|
const result = {};
|
||||||
|
for (const [key, value] of searchParams) {
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化查询字符串
|
||||||
|
function formatQuery(queryObject) {
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
for (const [key, value] of Object.entries(queryObject)) {
|
||||||
|
searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
return searchParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析路径名
|
||||||
|
function parsePath(path) {
|
||||||
|
return url.parse(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析主机名
|
||||||
|
function parseHost(host) {
|
||||||
|
const { hostname, port } = url.parse(`http://${host}`);
|
||||||
|
return { hostname, port };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 URL 参数
|
||||||
|
function getQueryParam(urlString, param) {
|
||||||
|
const { query } = url.parse(urlString, true);
|
||||||
|
return query[param];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 URL 参数
|
||||||
|
function addQueryParam(urlString, param, value) {
|
||||||
|
const parsedUrl = url.parse(urlString, true);
|
||||||
|
parsedUrl.query[param] = value;
|
||||||
|
parsedUrl.search = null; // 清除 search,以便 format 时使用 query
|
||||||
|
return url.format(parsedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除 URL 参数
|
||||||
|
function removeQueryParam(urlString, param) {
|
||||||
|
const parsedUrl = url.parse(urlString, true);
|
||||||
|
delete parsedUrl.query[param];
|
||||||
|
parsedUrl.search = null;
|
||||||
|
return url.format(parsedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否是绝对 URL
|
||||||
|
function isAbsolute(urlString) {
|
||||||
|
return url.parse(urlString).protocol !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 URL 的各个部分
|
||||||
|
function parseComponents(urlString) {
|
||||||
|
const { protocol, auth, hostname, port, pathname, search, hash } =
|
||||||
|
url.parse(urlString);
|
||||||
|
|
||||||
|
return {
|
||||||
|
protocol: protocol?.replace(":", ""),
|
||||||
|
auth,
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
pathname,
|
||||||
|
search: search?.replace("?", ""),
|
||||||
|
hash: hash?.replace("#", ""),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parse,
|
||||||
|
format,
|
||||||
|
parseQuery,
|
||||||
|
formatQuery,
|
||||||
|
parsePath,
|
||||||
|
parseHost,
|
||||||
|
getQueryParam,
|
||||||
|
addQueryParam,
|
||||||
|
removeQueryParam,
|
||||||
|
isAbsolute,
|
||||||
|
parseComponents,
|
||||||
|
};
|
1
plugin/lib/quickcomposer/zlib/index.js
Normal file
1
plugin/lib/quickcomposer/zlib/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require("../file/zlib");
|
126
plugin/lib/quickcomposer/zlib/zlib.js
Normal file
126
plugin/lib/quickcomposer/zlib/zlib.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
const zlib = require("zlib");
|
||||||
|
const { promisify } = require("util");
|
||||||
|
|
||||||
|
// 压缩方法
|
||||||
|
const gzip = promisify(zlib.gzip);
|
||||||
|
const deflate = promisify(zlib.deflate);
|
||||||
|
const brotliCompress = promisify(zlib.brotliCompress);
|
||||||
|
|
||||||
|
// 解压方法
|
||||||
|
const gunzip = promisify(zlib.gunzip);
|
||||||
|
const inflate = promisify(zlib.inflate);
|
||||||
|
const brotliDecompress = promisify(zlib.brotliDecompress);
|
||||||
|
|
||||||
|
// 同步方法
|
||||||
|
const gzipSync = zlib.gzipSync;
|
||||||
|
const gunzipSync = zlib.gunzipSync;
|
||||||
|
const deflateSync = zlib.deflateSync;
|
||||||
|
const inflateSync = zlib.inflateSync;
|
||||||
|
const brotliCompressSync = zlib.brotliCompressSync;
|
||||||
|
const brotliDecompressSync = zlib.brotliDecompressSync;
|
||||||
|
|
||||||
|
// 压缩选项
|
||||||
|
const defaultGzipOptions = {
|
||||||
|
level: zlib.constants.Z_DEFAULT_COMPRESSION,
|
||||||
|
memLevel: zlib.constants.Z_DEFAULT_MEMLEVEL,
|
||||||
|
strategy: zlib.constants.Z_DEFAULT_STRATEGY,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultBrotliOptions = {
|
||||||
|
params: {
|
||||||
|
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_GENERIC,
|
||||||
|
[zlib.constants.BROTLI_PARAM_QUALITY]:
|
||||||
|
zlib.constants.BROTLI_DEFAULT_QUALITY,
|
||||||
|
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 异步压缩函数
|
||||||
|
async function compressData(data, method, options = {}) {
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
switch (method) {
|
||||||
|
case "gzip":
|
||||||
|
return await gzip(buffer, { ...defaultGzipOptions, ...options });
|
||||||
|
case "deflate":
|
||||||
|
return await deflate(buffer, { ...defaultGzipOptions, ...options });
|
||||||
|
case "brotli":
|
||||||
|
return await brotliCompress(buffer, {
|
||||||
|
...defaultBrotliOptions,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
throw new Error("不支持的压缩方法");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`压缩失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步解压函数
|
||||||
|
async function decompressData(data, method, options = {}) {
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
switch (method) {
|
||||||
|
case "gzip":
|
||||||
|
return await gunzip(buffer, options);
|
||||||
|
case "deflate":
|
||||||
|
return await inflate(buffer, options);
|
||||||
|
case "brotli":
|
||||||
|
return await brotliDecompress(buffer, options);
|
||||||
|
default:
|
||||||
|
throw new Error("不支持的解压方法");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`解压失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步压缩函数
|
||||||
|
function compressDataSync(data, method, options = {}) {
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
switch (method) {
|
||||||
|
case "gzip":
|
||||||
|
return gzipSync(buffer, { ...defaultGzipOptions, ...options });
|
||||||
|
case "deflate":
|
||||||
|
return deflateSync(buffer, { ...defaultGzipOptions, ...options });
|
||||||
|
case "brotli":
|
||||||
|
return brotliCompressSync(buffer, {
|
||||||
|
...defaultBrotliOptions,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
throw new Error("不支持的压缩方法");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`压缩失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步解压函数
|
||||||
|
function decompressDataSync(data, method, options = {}) {
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
switch (method) {
|
||||||
|
case "gzip":
|
||||||
|
return gunzipSync(buffer, options);
|
||||||
|
case "deflate":
|
||||||
|
return inflateSync(buffer, options);
|
||||||
|
case "brotli":
|
||||||
|
return brotliDecompressSync(buffer, options);
|
||||||
|
default:
|
||||||
|
throw new Error("不支持的解压方法");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`解压失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
compressData,
|
||||||
|
decompressData,
|
||||||
|
compressDataSync,
|
||||||
|
decompressDataSync,
|
||||||
|
constants: zlib.constants,
|
||||||
|
};
|
@ -29,7 +29,7 @@ module.exports = configure(function (ctx) {
|
|||||||
boot: [],
|
boot: [],
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
|
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
|
||||||
css: ["app.css"],
|
css: ["app.css", "composer.css"],
|
||||||
|
|
||||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
extras: [
|
extras: [
|
||||||
@ -98,6 +98,7 @@ module.exports = configure(function (ctx) {
|
|||||||
path.join(__dirname, "./src/plugins")
|
path.join(__dirname, "./src/plugins")
|
||||||
);
|
);
|
||||||
chain.resolve.alias.set("js", path.join(__dirname, "./src/js"));
|
chain.resolve.alias.set("js", path.join(__dirname, "./src/js"));
|
||||||
|
chain.resolve.alias.set("css", path.join(__dirname, "./src/css"));
|
||||||
},
|
},
|
||||||
extendWebpack(cfg) {
|
extendWebpack(cfg) {
|
||||||
cfg.optimization.splitChunks = {
|
cfg.optimization.splitChunks = {
|
||||||
|
@ -90,7 +90,8 @@ export default defineComponent({
|
|||||||
if (type === "load") return this.loadFlow();
|
if (type === "load") return this.loadFlow();
|
||||||
const code = flow ? generateCode(flow) : generateCode(this.commandFlow);
|
const code = flow ? generateCode(flow) : generateCode(this.commandFlow);
|
||||||
this.$emit("use-composer", { type, code });
|
this.$emit("use-composer", { type, code });
|
||||||
if (type !== "run") this.$emit("update:modelValue", false);
|
if (type !== "run") return this.$emit("update:modelValue", false);
|
||||||
|
if (!code.includes("console.log")) quickcommand.showMessageBox("已运行");
|
||||||
},
|
},
|
||||||
saveFlow() {
|
saveFlow() {
|
||||||
const flow = window.lodashM.cloneDeep(this.commandFlow);
|
const flow = window.lodashM.cloneDeep(this.commandFlow);
|
||||||
@ -153,17 +154,6 @@ export default defineComponent({
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动美化 */
|
|
||||||
:deep(.q-scrollarea__thumb) {
|
|
||||||
width: 2px;
|
|
||||||
opacity: 0.4;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.q-scrollarea__thumb:hover) {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 动画效果 */
|
/* 动画效果 */
|
||||||
.command-section {
|
.command-section {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
@ -176,96 +166,4 @@ export default defineComponent({
|
|||||||
.body--dark .command-section:hover {
|
.body--dark .command-section:hover {
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 布局更加紧凑 */
|
|
||||||
/* 输入框高度及字体 */
|
|
||||||
.command-composer :deep(.q-field--filled:not(.q-textarea) .q-field__control),
|
|
||||||
.command-composer
|
|
||||||
:deep(.q-field--filled:not(.q-textarea) .q-field__control > *),
|
|
||||||
.command-composer
|
|
||||||
:deep(.q-field--filled:not(.q-field--labeled):not(.q-textarea)
|
|
||||||
.q-field__native) {
|
|
||||||
max-height: 36px !important;
|
|
||||||
min-height: 36px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command-composer :deep(.q-field--filled .q-field__control),
|
|
||||||
.command-composer :deep(.q-field--filled .q-field__control > *),
|
|
||||||
.command-composer :deep(.q-field--filled .q-field__native) {
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 输入框图标大小 */
|
|
||||||
.command-composer :deep(.q-field--filled .q-field__control .q-icon) {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 输入框标签字体大小,占位时的位置 */
|
|
||||||
.command-composer :deep(.q-field--filled .q-field__label) {
|
|
||||||
font-size: 11px;
|
|
||||||
top: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 输入框标签悬浮的位置 */
|
|
||||||
.command-composer :deep(.q-field--filled .q-field--float .q_field__label) {
|
|
||||||
transform: translateY(-35%) scale(0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 去除filled输入框边框 */
|
|
||||||
.command-composer :deep(.q-field--filled .q-field__control:before) {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 去除filled输入框下划线 */
|
|
||||||
.command-composer :deep(.q-field--filled .q-field__control:after) {
|
|
||||||
height: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 输入框背景颜色及内边距 */
|
|
||||||
.command-composer :deep(.q-field--filled .q-field__control) {
|
|
||||||
background: rgba(0, 0, 0, 0.03);
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 输入框聚焦时的背景颜色 */
|
|
||||||
.command-composer
|
|
||||||
:deep(.q-field--filled.q-field--highlighted .q-field__control) {
|
|
||||||
background: rgba(0, 0, 0, 0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 暗黑模式下的输入框背景颜色 */
|
|
||||||
.body--dark .command-composer :deep(.q-field--filled .q-field__control) {
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 暗黑模式下输入框聚焦时的背景颜色 */
|
|
||||||
.body--dark
|
|
||||||
.command-composer
|
|
||||||
:deep(.q-field--filled.q-field--highlighted .q-field__control) {
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* checkbox/toggle大小及字体 */
|
|
||||||
.command-composer :deep(.q-checkbox__label),
|
|
||||||
.command-composer :deep(.q-toggle__label) {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command-composer :deep(.q-checkbox__inner),
|
|
||||||
.command-composer :deep(.q-toggle__inner) {
|
|
||||||
font-size: 28px;
|
|
||||||
margin: 4px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 暗黑模式下的标签栏背景颜色 */
|
|
||||||
.body--dark .command-composer :deep(.q-tab),
|
|
||||||
.body--dark .command-composer :deep(.q-tab-panel) {
|
|
||||||
background-color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body--dark .command-composer :deep(.q-tab--inactive) {
|
|
||||||
opacity: 2;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
721
src/components/composer/developer/BufferEditor.vue
Normal file
721
src/components/composer/developer/BufferEditor.vue
Normal file
@ -0,0 +1,721 @@
|
|||||||
|
<template>
|
||||||
|
<div class="buffer-editor">
|
||||||
|
<!-- 操作类型选择 -->
|
||||||
|
<div class="operation-cards">
|
||||||
|
<div
|
||||||
|
v-for="op in operations"
|
||||||
|
:key="op.name"
|
||||||
|
:class="['operation-card', { active: argvs.operation === op.name }]"
|
||||||
|
@click="updateArgvs('operation', op.name)"
|
||||||
|
:data-value="op.name"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="op.icon"
|
||||||
|
size="16px"
|
||||||
|
:color="argvs.operation === op.name ? 'primary' : 'grey'"
|
||||||
|
/>
|
||||||
|
<div class="text-caption">{{ op.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作配置 -->
|
||||||
|
<div class="operation-options q-mt-sm">
|
||||||
|
<div class="options-container">
|
||||||
|
<!-- 创建 Buffer -->
|
||||||
|
<div v-if="argvs.operation === 'from'" class="row q-col-gutter-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.data"
|
||||||
|
@update:model-value="(val) => updateArgvs('data', val)"
|
||||||
|
label="数据"
|
||||||
|
icon="text_fields"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.encoding"
|
||||||
|
@update:model-value="(val) => updateArgvs('encoding', val)"
|
||||||
|
:options="encodings"
|
||||||
|
label="编码"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
class="col-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 转换为字符串 -->
|
||||||
|
<div
|
||||||
|
v-if="argvs.operation === 'toString'"
|
||||||
|
class="column q-col-gutter-sm"
|
||||||
|
>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.buffer"
|
||||||
|
@update:model-value="(val) => updateArgvs('buffer', val)"
|
||||||
|
label="Buffer"
|
||||||
|
icon="memory"
|
||||||
|
/>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.encoding"
|
||||||
|
@update:model-value="(val) => updateArgvs('encoding', val)"
|
||||||
|
:options="encodings"
|
||||||
|
label="编码"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.start"
|
||||||
|
@update:model-value="(val) => updateArgvs('start', val)"
|
||||||
|
label="起始位置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.end"
|
||||||
|
@update:model-value="(val) => updateArgvs('end', val)"
|
||||||
|
label="结束位置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 写入数据 -->
|
||||||
|
<div v-if="argvs.operation === 'write'" class="column q-col-gutter-sm">
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.buffer"
|
||||||
|
@update:model-value="(val) => updateArgvs('buffer', val)"
|
||||||
|
label="Buffer"
|
||||||
|
icon="memory"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.string"
|
||||||
|
@update:model-value="(val) => updateArgvs('string', val)"
|
||||||
|
label="要写入的字符串"
|
||||||
|
icon="edit"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.offset"
|
||||||
|
@update:model-value="(val) => updateArgvs('offset', val)"
|
||||||
|
label="偏移量"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.length"
|
||||||
|
@update:model-value="(val) => updateArgvs('length', val)"
|
||||||
|
label="长度"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.encoding"
|
||||||
|
@update:model-value="(val) => updateArgvs('encoding', val)"
|
||||||
|
:options="encodings"
|
||||||
|
label="编码"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 填充数据 -->
|
||||||
|
<div v-if="argvs.operation === 'fill'" class="column q-col-gutter-sm">
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.buffer"
|
||||||
|
@update:model-value="(val) => updateArgvs('buffer', val)"
|
||||||
|
label="Buffer"
|
||||||
|
icon="memory"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.value"
|
||||||
|
@update:model-value="(val) => updateArgvs('value', val)"
|
||||||
|
label="填充值"
|
||||||
|
icon="format_color_fill"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.offset"
|
||||||
|
@update:model-value="(val) => updateArgvs('offset', val)"
|
||||||
|
label="起始位置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.end"
|
||||||
|
@update:model-value="(val) => updateArgvs('end', val)"
|
||||||
|
label="结束位置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.encoding"
|
||||||
|
@update:model-value="(val) => updateArgvs('encoding', val)"
|
||||||
|
:options="encodings"
|
||||||
|
label="编码"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 复制数据 -->
|
||||||
|
<div v-if="argvs.operation === 'copy'" class="column q-col-gutter-sm">
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.source"
|
||||||
|
@update:model-value="(val) => updateArgvs('source', val)"
|
||||||
|
label="源Buffer"
|
||||||
|
icon="content_copy"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.target"
|
||||||
|
@update:model-value="(val) => updateArgvs('target', val)"
|
||||||
|
label="目标Buffer"
|
||||||
|
icon="save"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.targetStart"
|
||||||
|
@update:model-value="(val) => updateArgvs('targetStart', val)"
|
||||||
|
label="目标起始位置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.sourceStart"
|
||||||
|
@update:model-value="(val) => updateArgvs('sourceStart', val)"
|
||||||
|
label="源起始位置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.sourceEnd"
|
||||||
|
@update:model-value="(val) => updateArgvs('sourceEnd', val)"
|
||||||
|
label="源结束位置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 比较数据 -->
|
||||||
|
<div
|
||||||
|
v-if="argvs.operation === 'compare'"
|
||||||
|
class="column q-col-gutter-sm"
|
||||||
|
>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.buf1"
|
||||||
|
@update:model-value="(val) => updateArgvs('buf1', val)"
|
||||||
|
label="Buffer 1"
|
||||||
|
icon="memory"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.buf2"
|
||||||
|
@update:model-value="(val) => updateArgvs('buf2', val)"
|
||||||
|
label="Buffer 2"
|
||||||
|
icon="memory"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 连接 Buffer -->
|
||||||
|
<div v-if="argvs.operation === 'concat'" class="column q-gutter-sm">
|
||||||
|
<ArrayEditor
|
||||||
|
:model-value="argvs.buffers"
|
||||||
|
@update:model-value="(val) => updateArgvs('buffers', val)"
|
||||||
|
label="Buffer"
|
||||||
|
icon="memory"
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.totalLength"
|
||||||
|
@update:model-value="(val) => updateArgvs('totalLength', val)"
|
||||||
|
label="总长度(可选)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查找数据 -->
|
||||||
|
<div
|
||||||
|
v-if="argvs.operation === 'indexOf'"
|
||||||
|
class="column q-col-gutter-sm"
|
||||||
|
>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.buffer"
|
||||||
|
@update:model-value="(val) => updateArgvs('buffer', val)"
|
||||||
|
label="Buffer"
|
||||||
|
icon="memory"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.value"
|
||||||
|
@update:model-value="(val) => updateArgvs('value', val)"
|
||||||
|
label="要查找的值"
|
||||||
|
icon="search"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.byteOffset"
|
||||||
|
@update:model-value="(val) => updateArgvs('byteOffset', val)"
|
||||||
|
label="起始位置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.encoding"
|
||||||
|
@update:model-value="(val) => updateArgvs('encoding', val)"
|
||||||
|
:options="encodings"
|
||||||
|
label="编码"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 切片数据 -->
|
||||||
|
<div v-if="argvs.operation === 'slice'" class="row q-col-gutter-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.buffer"
|
||||||
|
@update:model-value="(val) => updateArgvs('buffer', val)"
|
||||||
|
label="Buffer"
|
||||||
|
icon="memory"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.start"
|
||||||
|
@update:model-value="(val) => updateArgvs('start', val)"
|
||||||
|
label="起始位置"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.end"
|
||||||
|
@update:model-value="(val) => updateArgvs('end', val)"
|
||||||
|
label="结束位置"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 交换字节序 -->
|
||||||
|
<div v-if="argvs.operation === 'swap'" class="row q-col-gutter-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.buffer"
|
||||||
|
@update:model-value="(val) => updateArgvs('buffer', val)"
|
||||||
|
label="Buffer"
|
||||||
|
class="col"
|
||||||
|
icon="memory"
|
||||||
|
/>
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.size"
|
||||||
|
@update:model-value="(val) => updateArgvs('size', val)"
|
||||||
|
:options="swapSizes"
|
||||||
|
label="字节大小"
|
||||||
|
class="col-3"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import { stringifyObject, parseFunction } from "js/composer/formatString";
|
||||||
|
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||||
|
import NumberInput from "components/composer/ui/NumberInput.vue";
|
||||||
|
import ArrayEditor from "components/composer/ui/ArrayEditor.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "BufferEditor",
|
||||||
|
components: {
|
||||||
|
VariableInput,
|
||||||
|
NumberInput,
|
||||||
|
ArrayEditor,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
operations: [
|
||||||
|
{ name: "from", label: "创建Buffer", icon: "add_box" },
|
||||||
|
{ name: "toString", label: "转换字符串", icon: "text_fields" },
|
||||||
|
{ name: "write", label: "写入数据", icon: "edit" },
|
||||||
|
{ name: "fill", label: "填充数据", icon: "format_color_fill" },
|
||||||
|
{ name: "copy", label: "复制数据", icon: "content_copy" },
|
||||||
|
{ name: "compare", label: "比较数据", icon: "compare" },
|
||||||
|
{ name: "concat", label: "连接Buffer", icon: "merge" },
|
||||||
|
{ name: "indexOf", label: "查找数据", icon: "search" },
|
||||||
|
{ name: "slice", label: "切片数据", icon: "content_cut" },
|
||||||
|
{ name: "swap", label: "交换字节序", icon: "swap_horiz" },
|
||||||
|
],
|
||||||
|
encodings: [
|
||||||
|
{ label: "UTF-8", value: "utf8" },
|
||||||
|
{ label: "UTF-16LE", value: "utf16le" },
|
||||||
|
{ label: "Latin1", value: "latin1" },
|
||||||
|
{ label: "Base64", value: "base64" },
|
||||||
|
{ label: "Hex", value: "hex" },
|
||||||
|
{ label: "ASCII", value: "ascii" },
|
||||||
|
{ label: "Binary", value: "binary" },
|
||||||
|
{ label: "UCS-2", value: "ucs2" },
|
||||||
|
],
|
||||||
|
swapSizes: [
|
||||||
|
{ label: "16位", value: 16 },
|
||||||
|
{ label: "32位", value: 32 },
|
||||||
|
{ label: "64位", value: 64 },
|
||||||
|
],
|
||||||
|
defaultArgvs: {
|
||||||
|
operation: "from",
|
||||||
|
data: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
buffer: {
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
encoding: "utf8",
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
string: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
offset: 0,
|
||||||
|
length: 0,
|
||||||
|
value: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
targetStart: 0,
|
||||||
|
sourceStart: 0,
|
||||||
|
sourceEnd: 0,
|
||||||
|
buf1: {
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
buf2: {
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
buffers: [
|
||||||
|
{
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
totalLength: undefined,
|
||||||
|
byteOffset: 0,
|
||||||
|
size: 16,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
argvs: {
|
||||||
|
get() {
|
||||||
|
return (
|
||||||
|
this.modelValue.argvs ||
|
||||||
|
this.parseCodeToArgvs(this.modelValue.code) || {
|
||||||
|
...this.defaultArgvs,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.updateModelValue(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateCode(argvs = this.argvs) {
|
||||||
|
switch (argvs.operation) {
|
||||||
|
case "from":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.data
|
||||||
|
)}, "${argvs.encoding}")`;
|
||||||
|
|
||||||
|
case "toString":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.buffer
|
||||||
|
)}, "${argvs.encoding}", ${argvs.start}, ${argvs.end})`;
|
||||||
|
|
||||||
|
case "write":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.buffer
|
||||||
|
)}, ${stringifyObject(argvs.string)}, ${argvs.offset}, ${
|
||||||
|
argvs.length
|
||||||
|
}, "${argvs.encoding}")`;
|
||||||
|
|
||||||
|
case "fill":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.buffer
|
||||||
|
)}, ${stringifyObject(argvs.value)}, ${argvs.offset}, ${
|
||||||
|
argvs.end
|
||||||
|
}, "${argvs.encoding}")`;
|
||||||
|
|
||||||
|
case "copy":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.source
|
||||||
|
)}, ${stringifyObject(argvs.target)}, ${argvs.targetStart}, ${
|
||||||
|
argvs.sourceStart
|
||||||
|
}, ${argvs.sourceEnd})`;
|
||||||
|
|
||||||
|
case "compare":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.buf1
|
||||||
|
)}, ${stringifyObject(argvs.buf2)})`;
|
||||||
|
|
||||||
|
case "concat":
|
||||||
|
const buffersStr = argvs.buffers
|
||||||
|
.map((buf) => stringifyObject(buf))
|
||||||
|
.join(", ");
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}([${buffersStr}]${
|
||||||
|
argvs.totalLength !== undefined ? `, ${argvs.totalLength}` : ""
|
||||||
|
})`;
|
||||||
|
|
||||||
|
case "indexOf":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.buffer
|
||||||
|
)}, ${stringifyObject(argvs.value)}, ${argvs.byteOffset}, "${
|
||||||
|
argvs.encoding
|
||||||
|
}")`;
|
||||||
|
|
||||||
|
case "slice":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.buffer
|
||||||
|
)}, ${argvs.start}, ${argvs.end})`;
|
||||||
|
|
||||||
|
case "swap":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.buffer
|
||||||
|
)}, ${argvs.size})`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}()`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseCodeToArgvs(code) {
|
||||||
|
if (!code) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 定义需要使用variable格式的路径
|
||||||
|
const variableFormatPaths = ["arg0", "arg0[*]"];
|
||||||
|
|
||||||
|
const subFunc = code.match(/buffer\.(\w+)\((.*)\)/);
|
||||||
|
|
||||||
|
switch (subFunc[1]) {
|
||||||
|
case "write":
|
||||||
|
case "fill":
|
||||||
|
case "copy":
|
||||||
|
case "compare":
|
||||||
|
case "indexOf":
|
||||||
|
variableFormatPaths.push("arg1");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 parseFunction 解析代码
|
||||||
|
const result = parseFunction(code, { variableFormatPaths });
|
||||||
|
if (!result) return this.defaultArgvs;
|
||||||
|
|
||||||
|
const operation = result.name.split(".").pop();
|
||||||
|
const args = result.args;
|
||||||
|
|
||||||
|
const newArgvs = {
|
||||||
|
...this.defaultArgvs,
|
||||||
|
operation,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (operation) {
|
||||||
|
case "from":
|
||||||
|
newArgvs.data = args[0];
|
||||||
|
newArgvs.encoding = args[1]?.value || "utf8";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "toString":
|
||||||
|
newArgvs.buffer = args[0];
|
||||||
|
newArgvs.encoding = args[1]?.value || "utf8";
|
||||||
|
newArgvs.start = args[2] ?? 0;
|
||||||
|
newArgvs.end = args[3] ?? 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "write":
|
||||||
|
newArgvs.buffer = args[0];
|
||||||
|
newArgvs.string = args[1];
|
||||||
|
newArgvs.offset = args[2] ?? 0;
|
||||||
|
newArgvs.length = args[3] ?? 0;
|
||||||
|
newArgvs.encoding = args[4]?.value || "utf8";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "fill":
|
||||||
|
newArgvs.buffer = args[0];
|
||||||
|
newArgvs.value = args[1];
|
||||||
|
newArgvs.offset = args[2] ?? 0;
|
||||||
|
newArgvs.end = args[3] ?? 0;
|
||||||
|
newArgvs.encoding = args[4]?.value || "utf8";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "copy":
|
||||||
|
newArgvs.source = args[0];
|
||||||
|
newArgvs.target = args[1];
|
||||||
|
newArgvs.targetStart = args[2] ?? 0;
|
||||||
|
newArgvs.sourceStart = args[3] ?? 0;
|
||||||
|
newArgvs.sourceEnd = args[4] ?? 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "compare":
|
||||||
|
newArgvs.buf1 = args[0];
|
||||||
|
newArgvs.buf2 = args[1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "concat":
|
||||||
|
if (Array.isArray(args[0])) {
|
||||||
|
newArgvs.buffers = args[0];
|
||||||
|
}
|
||||||
|
newArgvs.totalLength = args[1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "indexOf":
|
||||||
|
newArgvs.buffer = args[0];
|
||||||
|
newArgvs.value = args[1];
|
||||||
|
newArgvs.byteOffset = args[2] ?? 0;
|
||||||
|
newArgvs.encoding = args[3]?.value || "utf8";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "slice":
|
||||||
|
newArgvs.buffer = args[0];
|
||||||
|
newArgvs.start = args[1] ?? 0;
|
||||||
|
newArgvs.end = args[2] ?? 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "swap":
|
||||||
|
newArgvs.buffer = args[0];
|
||||||
|
newArgvs.size = args[1] ?? 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newArgvs;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("解析Buffer参数失败:", e);
|
||||||
|
return this.defaultArgvs;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateArgvs(key, value) {
|
||||||
|
this.argvs = {
|
||||||
|
...this.argvs,
|
||||||
|
[key]: value,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getSummary(argvs) {
|
||||||
|
const op = this.operations.find(
|
||||||
|
(op) => op.name === argvs.operation
|
||||||
|
)?.label;
|
||||||
|
return op;
|
||||||
|
},
|
||||||
|
updateModelValue(argvs) {
|
||||||
|
this.$emit("update:modelValue", {
|
||||||
|
...this.modelValue,
|
||||||
|
summary: this.getSummary(argvs),
|
||||||
|
argvs,
|
||||||
|
code: this.generateCode(argvs),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.modelValue.argvs && !this.modelValue.code) {
|
||||||
|
this.updateModelValue(this.defaultArgvs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
"argvs.operation": {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document
|
||||||
|
.querySelector(`.operation-card[data-value="${newVal}"]`)
|
||||||
|
?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
inline: "nearest",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.buffer-editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-container {
|
||||||
|
min-height: 32px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -78,7 +78,7 @@
|
|||||||
<template v-if="argvs.operation === 'read'">
|
<template v-if="argvs.operation === 'read'">
|
||||||
<div class="row q-gutter-sm">
|
<div class="row q-gutter-sm">
|
||||||
<q-select
|
<q-select
|
||||||
v-model="argvs.encoding"
|
:model-value="argvs.encoding || 'Buffer'"
|
||||||
:options="encodingOptions"
|
:options="encodingOptions"
|
||||||
label="编码"
|
label="编码"
|
||||||
dense
|
dense
|
||||||
@ -392,6 +392,7 @@ const ENCODING_OPTIONS = [
|
|||||||
{ label: "GB2312", value: "gb2312" },
|
{ label: "GB2312", value: "gb2312" },
|
||||||
{ label: "GBK", value: "gbk" },
|
{ label: "GBK", value: "gbk" },
|
||||||
{ label: "GB18030", value: "gb18030" },
|
{ label: "GB18030", value: "gb18030" },
|
||||||
|
{ label: "Buffer", value: null },
|
||||||
{ label: "Big5", value: "big5" },
|
{ label: "Big5", value: "big5" },
|
||||||
{ label: "ASCII", value: "ascii" },
|
{ label: "ASCII", value: "ascii" },
|
||||||
{ label: "Latin1", value: "latin1" },
|
{ label: "Latin1", value: "latin1" },
|
||||||
|
338
src/components/composer/file/ZlibEditor.vue
Normal file
338
src/components/composer/file/ZlibEditor.vue
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
<template>
|
||||||
|
<div class="zlib-editor">
|
||||||
|
<!-- 操作类型选择 -->
|
||||||
|
<div class="row items-center q-gutter-x-xs">
|
||||||
|
<!-- 数据输入 -->
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.data"
|
||||||
|
@update:model-value="(val) => updateArgvs('data', val)"
|
||||||
|
label="要处理的数据"
|
||||||
|
class="col"
|
||||||
|
icon="data_object"
|
||||||
|
/>
|
||||||
|
<div class="col-auto row items-center q-gutter-x-xs">
|
||||||
|
<div
|
||||||
|
v-for="op in operations"
|
||||||
|
:key="op.name"
|
||||||
|
:class="['operation-card', { active: argvs.operation === op.name }]"
|
||||||
|
@click="updateArgvs('operation', op.name)"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="op.icon"
|
||||||
|
size="16px"
|
||||||
|
:color="argvs.operation === op.name ? 'primary' : 'grey'"
|
||||||
|
/>
|
||||||
|
<div class="text-caption">{{ op.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 操作配置 -->
|
||||||
|
<div class="operation-options">
|
||||||
|
<div class="options-container">
|
||||||
|
<!-- 压缩选项 -->
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<!-- 压缩方法选择 -->
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.method"
|
||||||
|
@update:model-value="updateArgvs('method', $event)"
|
||||||
|
:options="methods"
|
||||||
|
label="压缩方法"
|
||||||
|
class="col-3"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
<!-- Gzip/Deflate 选项 -->
|
||||||
|
<template v-if="argvs.method !== 'brotli'">
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.options.level"
|
||||||
|
@update:model-value="(val) => updateArgvs('options.level', val)"
|
||||||
|
:options="compressionLevels"
|
||||||
|
label="压缩级别"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.options.memLevel"
|
||||||
|
@update:model-value="
|
||||||
|
(val) => updateArgvs('options.memLevel', val)
|
||||||
|
"
|
||||||
|
:options="memoryLevels"
|
||||||
|
label="内存级别"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.options.strategy"
|
||||||
|
@update:model-value="
|
||||||
|
(val) => updateArgvs('options.strategy', val)
|
||||||
|
"
|
||||||
|
:options="strategies"
|
||||||
|
label="压缩策略"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Brotli 选项 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.options.params.mode"
|
||||||
|
@update:model-value="
|
||||||
|
(val) => updateArgvs('options.params.mode', val)
|
||||||
|
"
|
||||||
|
:options="brotliModes"
|
||||||
|
label="压缩模式"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.options.params.quality"
|
||||||
|
@update:model-value="
|
||||||
|
(val) => updateArgvs('options.params.quality', val)
|
||||||
|
"
|
||||||
|
:options="brotliQualities"
|
||||||
|
label="压缩质量"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
<NumberInput
|
||||||
|
:model-value="argvs.options.params.sizeHint"
|
||||||
|
@update:model-value="
|
||||||
|
(val) => updateArgvs('options.params.sizeHint', val)
|
||||||
|
"
|
||||||
|
label="大小提示"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import { stringifyObject, parseFunction } from "js/composer/formatString";
|
||||||
|
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||||
|
import NumberInput from "components/composer/ui/NumberInput.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "ZlibEditor",
|
||||||
|
components: {
|
||||||
|
VariableInput,
|
||||||
|
NumberInput,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
operations: [
|
||||||
|
{ name: "compressData", label: "压缩", icon: "compress" },
|
||||||
|
{ name: "decompressData", label: "解压", icon: "expand" },
|
||||||
|
],
|
||||||
|
methods: [
|
||||||
|
{ label: "Gzip", value: "gzip" },
|
||||||
|
{ label: "Deflate", value: "deflate" },
|
||||||
|
{ label: "Brotli", value: "brotli" },
|
||||||
|
],
|
||||||
|
compressionLevels: [
|
||||||
|
{ label: "默认压缩", value: -1 },
|
||||||
|
{ label: "不压缩", value: 0 },
|
||||||
|
{ label: "最快压缩", value: 1 },
|
||||||
|
{ label: "最佳压缩", value: 9 },
|
||||||
|
],
|
||||||
|
memoryLevels: [
|
||||||
|
{ label: "默认内存", value: 8 },
|
||||||
|
{ label: "最小内存", value: 1 },
|
||||||
|
{ label: "最大内存", value: 9 },
|
||||||
|
],
|
||||||
|
strategies: [
|
||||||
|
{ label: "默认策略", value: 0 },
|
||||||
|
{ label: "过滤策略", value: 1 },
|
||||||
|
{ label: "哈夫曼策略", value: 2 },
|
||||||
|
{ label: "RLE策略", value: 3 },
|
||||||
|
{ label: "固定策略", value: 4 },
|
||||||
|
],
|
||||||
|
brotliModes: [
|
||||||
|
{ label: "通用模式", value: 0 },
|
||||||
|
{ label: "文本模式", value: 1 },
|
||||||
|
{ label: "字体模式", value: 2 },
|
||||||
|
],
|
||||||
|
brotliQualities: [
|
||||||
|
{ label: "默认质量", value: 11 },
|
||||||
|
{ label: "最快压缩", value: 0 },
|
||||||
|
{ label: "最佳压缩", value: 11 },
|
||||||
|
],
|
||||||
|
defaultArgvs: {
|
||||||
|
operation: "compressData",
|
||||||
|
method: "gzip",
|
||||||
|
data: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
level: -1,
|
||||||
|
memLevel: 8,
|
||||||
|
strategy: 0,
|
||||||
|
params: {
|
||||||
|
mode: 0,
|
||||||
|
quality: 11,
|
||||||
|
sizeHint: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
argvs: {
|
||||||
|
get() {
|
||||||
|
return (
|
||||||
|
this.modelValue.argvs ||
|
||||||
|
this.parseCodeToArgvs(this.modelValue.code) || {
|
||||||
|
...this.defaultArgvs,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.updateModelValue(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateCode(argvs = this.argvs) {
|
||||||
|
const options =
|
||||||
|
argvs.method === "brotli"
|
||||||
|
? { params: argvs.options.params }
|
||||||
|
: {
|
||||||
|
level: argvs.options.level,
|
||||||
|
memLevel: argvs.options.memLevel,
|
||||||
|
strategy: argvs.options.strategy,
|
||||||
|
};
|
||||||
|
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.data
|
||||||
|
)}, "${argvs.method}", ${stringifyObject(options)})`;
|
||||||
|
},
|
||||||
|
parseCodeToArgvs(code) {
|
||||||
|
if (!code) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 定义需要使用variable格式的路径
|
||||||
|
const variableFormatPaths = [
|
||||||
|
"arg0", // 数据参数
|
||||||
|
];
|
||||||
|
|
||||||
|
// 使用 parseFunction 解析代码
|
||||||
|
const result = parseFunction(code, { variableFormatPaths });
|
||||||
|
if (!result) return this.defaultArgvs;
|
||||||
|
|
||||||
|
const operation = result.name.split(".").pop();
|
||||||
|
const [data, method, options] = result.args;
|
||||||
|
|
||||||
|
const newArgvs = {
|
||||||
|
...this.defaultArgvs,
|
||||||
|
operation,
|
||||||
|
data,
|
||||||
|
method: method?.value || "gzip",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
if (method?.value === "brotli") {
|
||||||
|
newArgvs.options = {
|
||||||
|
params: options.params || this.defaultArgvs.options.params,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newArgvs.options = {
|
||||||
|
level: options.level ?? this.defaultArgvs.options.level,
|
||||||
|
memLevel: options.memLevel ?? this.defaultArgvs.options.memLevel,
|
||||||
|
strategy: options.strategy ?? this.defaultArgvs.options.strategy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newArgvs;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("解析Zlib参数失败:", e);
|
||||||
|
return this.defaultArgvs;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateArgvs(key, value) {
|
||||||
|
this.argvs = {
|
||||||
|
...this.argvs,
|
||||||
|
[key]: value,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getSummary(argvs) {
|
||||||
|
const op = this.operations.find(
|
||||||
|
(op) => op.name === argvs.operation
|
||||||
|
)?.label;
|
||||||
|
const method = this.methods.find((m) => m.value === argvs.method)?.label;
|
||||||
|
return `${op} (${method})`;
|
||||||
|
},
|
||||||
|
updateModelValue(argvs) {
|
||||||
|
this.$emit("update:modelValue", {
|
||||||
|
...this.modelValue,
|
||||||
|
summary: this.getSummary(argvs),
|
||||||
|
argvs,
|
||||||
|
code: this.generateCode(argvs),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.modelValue.argvs && !this.modelValue.code) {
|
||||||
|
this.updateModelValue(this.defaultArgvs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.zlib-editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-container {
|
||||||
|
min-height: 32px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
272
src/components/composer/network/DnsEditor.vue
Normal file
272
src/components/composer/network/DnsEditor.vue
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dns-editor">
|
||||||
|
<!-- 操作类型选择 -->
|
||||||
|
<div class="operation-cards">
|
||||||
|
<div
|
||||||
|
v-for="op in operations"
|
||||||
|
:key="op.name"
|
||||||
|
:class="['operation-card', { active: argvs.operation === op.name }]"
|
||||||
|
@click="updateArgvs('operation', op.name)"
|
||||||
|
:data-value="op.name"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="op.icon"
|
||||||
|
size="16px"
|
||||||
|
:color="argvs.operation === op.name ? 'primary' : 'grey'"
|
||||||
|
/>
|
||||||
|
<div class="text-caption">{{ op.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作配置 -->
|
||||||
|
<div class="operation-options">
|
||||||
|
<div class="options-container row">
|
||||||
|
<!-- 主机名输入 -->
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.hostname"
|
||||||
|
@update:model-value="(val) => updateArgvs('hostname', val)"
|
||||||
|
label="主机名"
|
||||||
|
icon="dns"
|
||||||
|
class="col"
|
||||||
|
v-if="needsHostname"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- IP地址输入 -->
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.ip"
|
||||||
|
@update:model-value="(val) => updateArgvs('ip', val)"
|
||||||
|
label="IP地址"
|
||||||
|
icon="router"
|
||||||
|
v-if="argvs.operation === 'reverseResolve'"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- lookup 选项 -->
|
||||||
|
<div
|
||||||
|
class="row items-center col-5"
|
||||||
|
v-if="argvs.operation === 'lookupHost'"
|
||||||
|
>
|
||||||
|
<div class="col-6">
|
||||||
|
<q-select
|
||||||
|
:model-value="argvs.options.family"
|
||||||
|
@update:model-value="(val) => updateArgvs('options.family', val)"
|
||||||
|
:options="families"
|
||||||
|
label="IP版本"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 q-px-sm">
|
||||||
|
<q-toggle
|
||||||
|
:model-value="argvs.options.all"
|
||||||
|
@update:model-value="(val) => updateArgvs('options.all', val)"
|
||||||
|
label="返回所有地址"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import { stringifyObject, parseFunction } from "js/composer/formatString";
|
||||||
|
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "DnsEditor",
|
||||||
|
components: {
|
||||||
|
VariableInput,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
operations: [
|
||||||
|
{ name: "lookupHost", label: "DNS查询", icon: "search" },
|
||||||
|
{ name: "resolveAll", label: "解析所有记录", icon: "all_inclusive" },
|
||||||
|
{ name: "resolveIpv4", label: "解析IPv4", icon: "filter_4" },
|
||||||
|
{ name: "resolveIpv6", label: "解析IPv6", icon: "filter_6" },
|
||||||
|
{ name: "resolveMxRecords", label: "解析MX记录", icon: "mail" },
|
||||||
|
{
|
||||||
|
name: "resolveTxtRecords",
|
||||||
|
label: "解析TXT记录",
|
||||||
|
icon: "text_fields",
|
||||||
|
},
|
||||||
|
{ name: "resolveNsRecords", label: "解析NS记录", icon: "dns" },
|
||||||
|
{ name: "resolveCnameRecords", label: "解析CNAME记录", icon: "link" },
|
||||||
|
{ name: "reverseResolve", label: "反向解析", icon: "swap_horiz" },
|
||||||
|
],
|
||||||
|
families: [
|
||||||
|
{ label: "自动", value: 0 },
|
||||||
|
{ label: "IPv4", value: 4 },
|
||||||
|
{ label: "IPv6", value: 6 },
|
||||||
|
],
|
||||||
|
defaultArgvs: {
|
||||||
|
operation: "lookupHost",
|
||||||
|
hostname: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
ip: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
family: 0,
|
||||||
|
all: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
argvs: {
|
||||||
|
get() {
|
||||||
|
return (
|
||||||
|
this.modelValue.argvs ||
|
||||||
|
this.parseCodeToArgvs(this.modelValue.code) || {
|
||||||
|
...this.defaultArgvs,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.updateModelValue(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
needsHostname() {
|
||||||
|
return this.argvs.operation !== "reverseResolve";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateCode(argvs = this.argvs) {
|
||||||
|
switch (argvs.operation) {
|
||||||
|
case "lookupHost":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.hostname
|
||||||
|
)}, ${stringifyObject(argvs.options)})`;
|
||||||
|
|
||||||
|
case "reverseResolve":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.ip
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.hostname
|
||||||
|
)})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseCodeToArgvs(code) {
|
||||||
|
if (!code) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 定义需要使用variable格式的路径
|
||||||
|
const variableFormatPaths = [
|
||||||
|
"arg0", // 主机名/IP参数
|
||||||
|
"arg1.**", // options对象的所有属性
|
||||||
|
];
|
||||||
|
|
||||||
|
// 使用 parseFunction 解析代码
|
||||||
|
const result = parseFunction(code, { variableFormatPaths });
|
||||||
|
if (!result) return this.defaultArgvs;
|
||||||
|
|
||||||
|
const operation = result.name.split(".").pop();
|
||||||
|
const [firstArg, secondArg] = result.args;
|
||||||
|
|
||||||
|
const newArgvs = {
|
||||||
|
...this.defaultArgvs,
|
||||||
|
operation,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (operation === "reverseResolve") {
|
||||||
|
newArgvs.ip = firstArg;
|
||||||
|
} else {
|
||||||
|
newArgvs.hostname = firstArg;
|
||||||
|
if (operation === "lookupHost" && secondArg) {
|
||||||
|
newArgvs.options = {
|
||||||
|
family: secondArg.family ?? this.defaultArgvs.options.family,
|
||||||
|
all: secondArg.all ?? this.defaultArgvs.options.all,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newArgvs;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("解析DNS参数失败:", e);
|
||||||
|
return this.defaultArgvs;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateArgvs(key, value) {
|
||||||
|
this.argvs = {
|
||||||
|
...this.argvs,
|
||||||
|
[key]: value,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getSummary(argvs) {
|
||||||
|
const op = this.operations.find(
|
||||||
|
(op) => op.name === argvs.operation
|
||||||
|
)?.label;
|
||||||
|
return op === "反向解析"
|
||||||
|
? "反向解析 " + argvs.ip.value
|
||||||
|
: op + " " + argvs.hostname.value;
|
||||||
|
},
|
||||||
|
updateModelValue(argvs) {
|
||||||
|
this.$emit("update:modelValue", {
|
||||||
|
...this.modelValue,
|
||||||
|
summary: this.getSummary(argvs),
|
||||||
|
argvs,
|
||||||
|
code: this.generateCode(argvs),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.modelValue.argvs && !this.modelValue.code) {
|
||||||
|
this.updateModelValue(this.defaultArgvs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
"argvs.operation": {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document
|
||||||
|
.querySelector(`.operation-card[data-value="${newVal}"]`)
|
||||||
|
?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
inline: "nearest",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dns-editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-container {
|
||||||
|
min-height: 32px;
|
||||||
|
gap: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .operation-card {
|
||||||
|
min-width: 95px;
|
||||||
|
}
|
||||||
|
</style>
|
487
src/components/composer/network/UrlEditor.vue
Normal file
487
src/components/composer/network/UrlEditor.vue
Normal file
@ -0,0 +1,487 @@
|
|||||||
|
<template>
|
||||||
|
<div class="url-editor">
|
||||||
|
<!-- 操作类型选择 -->
|
||||||
|
<div class="operation-cards">
|
||||||
|
<div
|
||||||
|
v-for="op in operations"
|
||||||
|
:key="op.name"
|
||||||
|
:class="['operation-card', { active: argvs.operation === op.name }]"
|
||||||
|
@click="updateArgvs('operation', op.name)"
|
||||||
|
:data-value="op.name"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="op.icon"
|
||||||
|
size="16px"
|
||||||
|
:color="argvs.operation === op.name ? 'primary' : 'grey'"
|
||||||
|
/>
|
||||||
|
<div class="text-caption">{{ op.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作配置 -->
|
||||||
|
<div class="operation-options q-mt-sm">
|
||||||
|
<div class="options-container">
|
||||||
|
<!-- URL 输入 -->
|
||||||
|
<template v-if="needsUrlInput">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.url"
|
||||||
|
@update:model-value="(val) => updateArgvs('url', val)"
|
||||||
|
label="URL"
|
||||||
|
icon="link"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 查询字符串输入 -->
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
argvs.operation === 'parseQuery' ||
|
||||||
|
argvs.operation === 'formatQuery'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template v-if="argvs.operation === 'parseQuery'">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.query"
|
||||||
|
@update:model-value="(val) => updateArgvs('query', val)"
|
||||||
|
label="查询字符串"
|
||||||
|
icon="search"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<DictEditor
|
||||||
|
:model-value="argvs.queryParams"
|
||||||
|
@update:model-value="(val) => updateArgvs('queryParams', val)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 参数操作的参数输入 -->
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
['getQueryParam', 'addQueryParam', 'removeQueryParam'].includes(
|
||||||
|
argvs.operation
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.param"
|
||||||
|
@update:model-value="(val) => updateArgvs('param', val)"
|
||||||
|
label="参数名"
|
||||||
|
icon="key"
|
||||||
|
class="q-mt-sm"
|
||||||
|
/>
|
||||||
|
<template v-if="argvs.operation === 'addQueryParam'">
|
||||||
|
<div class="q-mt-sm">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.value"
|
||||||
|
@update:model-value="(val) => updateArgvs('value', val)"
|
||||||
|
label="参数值"
|
||||||
|
icon="text_fields"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 主机名输入 -->
|
||||||
|
<template v-if="argvs.operation === 'parseHost'">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.host"
|
||||||
|
@update:model-value="(val) => updateArgvs('host', val)"
|
||||||
|
label="主机名"
|
||||||
|
icon="dns"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 路径输入 -->
|
||||||
|
<template v-if="argvs.operation === 'parsePath'">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.path"
|
||||||
|
@update:model-value="(val) => updateArgvs('path', val)"
|
||||||
|
label="路径"
|
||||||
|
icon="folder"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- URL 对象输入 -->
|
||||||
|
<template v-if="argvs.operation === 'format'">
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.urlObject.protocol"
|
||||||
|
@update:model-value="(val) => updateUrlObject('protocol', val)"
|
||||||
|
label="协议"
|
||||||
|
icon="security"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.urlObject.auth"
|
||||||
|
@update:model-value="(val) => updateUrlObject('auth', val)"
|
||||||
|
label="认证信息"
|
||||||
|
icon="person"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.urlObject.hostname"
|
||||||
|
@update:model-value="(val) => updateUrlObject('hostname', val)"
|
||||||
|
label="主机名"
|
||||||
|
icon="dns"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.urlObject.port"
|
||||||
|
@update:model-value="(val) => updateUrlObject('port', val)"
|
||||||
|
label="端口"
|
||||||
|
icon="settings_ethernet"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.urlObject.pathname"
|
||||||
|
@update:model-value="(val) => updateUrlObject('pathname', val)"
|
||||||
|
label="路径"
|
||||||
|
icon="folder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.urlObject.search"
|
||||||
|
@update:model-value="(val) => updateUrlObject('search', val)"
|
||||||
|
label="查询字符串"
|
||||||
|
icon="search"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<VariableInput
|
||||||
|
:model-value="argvs.urlObject.hash"
|
||||||
|
@update:model-value="(val) => updateUrlObject('hash', val)"
|
||||||
|
label="锚点"
|
||||||
|
icon="tag"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import { stringifyObject, parseFunction } from "js/composer/formatString";
|
||||||
|
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||||
|
import DictEditor from "components/composer/ui/DictEditor.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "UrlEditor",
|
||||||
|
components: {
|
||||||
|
VariableInput,
|
||||||
|
DictEditor,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
operations: [
|
||||||
|
{ name: "parse", label: "解析URL", icon: "link_off" },
|
||||||
|
{ name: "format", label: "格式化URL", icon: "link" },
|
||||||
|
{ name: "parseQuery", label: "解析查询字符串", icon: "search" },
|
||||||
|
{ name: "formatQuery", label: "格式化查询字符串", icon: "edit" },
|
||||||
|
{ name: "parsePath", label: "解析路径", icon: "folder_open" },
|
||||||
|
{ name: "parseHost", label: "解析主机名", icon: "dns" },
|
||||||
|
{ name: "getQueryParam", label: "获取参数", icon: "find_in_page" },
|
||||||
|
{ name: "addQueryParam", label: "添加参数", icon: "add_circle" },
|
||||||
|
{ name: "removeQueryParam", label: "移除参数", icon: "remove_circle" },
|
||||||
|
{ name: "isAbsolute", label: "检查绝对URL", icon: "check_circle" },
|
||||||
|
{ name: "parseComponents", label: "解析组成部分", icon: "category" },
|
||||||
|
],
|
||||||
|
defaultArgvs: {
|
||||||
|
operation: "parse",
|
||||||
|
url: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
queryParams: {},
|
||||||
|
param: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
urlObject: {
|
||||||
|
protocol: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
hostname: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
pathname: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
hash: {
|
||||||
|
value: "",
|
||||||
|
isString: true,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
argvs: {
|
||||||
|
get() {
|
||||||
|
return (
|
||||||
|
this.modelValue.argvs ||
|
||||||
|
this.parseCodeToArgvs(this.modelValue.code) || {
|
||||||
|
...this.defaultArgvs,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.updateModelValue(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
needsUrlInput() {
|
||||||
|
return [
|
||||||
|
"parse",
|
||||||
|
"getQueryParam",
|
||||||
|
"addQueryParam",
|
||||||
|
"removeQueryParam",
|
||||||
|
"isAbsolute",
|
||||||
|
"parseComponents",
|
||||||
|
].includes(this.argvs.operation);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateCode(argvs = this.argvs) {
|
||||||
|
switch (argvs.operation) {
|
||||||
|
case "parse":
|
||||||
|
case "isAbsolute":
|
||||||
|
case "parseComponents":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.url
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
case "format":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.urlObject
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
case "parseQuery":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.query
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
case "formatQuery":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.queryParams
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
case "parsePath":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.path
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
case "parseHost":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.host
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
case "getQueryParam":
|
||||||
|
case "removeQueryParam":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.url
|
||||||
|
)}, ${stringifyObject(argvs.param)})`;
|
||||||
|
|
||||||
|
case "addQueryParam":
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}(${stringifyObject(
|
||||||
|
argvs.url
|
||||||
|
)}, ${stringifyObject(argvs.param)}, ${stringifyObject(
|
||||||
|
argvs.value
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return `${this.modelValue.value}.${argvs.operation}()`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseCodeToArgvs(code) {
|
||||||
|
if (!code) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const variableFormatPaths = ["arg*", "arg*.**"];
|
||||||
|
|
||||||
|
const result = parseFunction(code, { variableFormatPaths });
|
||||||
|
if (!result) return this.defaultArgvs;
|
||||||
|
|
||||||
|
const operation = result.name.split(".").pop();
|
||||||
|
const [firstArg, secondArg, thirdArg] = result.args;
|
||||||
|
|
||||||
|
const newArgvs = {
|
||||||
|
...this.defaultArgvs,
|
||||||
|
operation,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (operation) {
|
||||||
|
case "parse":
|
||||||
|
case "isAbsolute":
|
||||||
|
case "parseComponents":
|
||||||
|
newArgvs.url = firstArg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "format":
|
||||||
|
newArgvs.urlObject = firstArg || this.defaultArgvs.urlObject;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "parseQuery":
|
||||||
|
newArgvs.query = firstArg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "formatQuery":
|
||||||
|
if (firstArg) {
|
||||||
|
newArgvs.queryParams = firstArg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "parsePath":
|
||||||
|
newArgvs.path = firstArg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "parseHost":
|
||||||
|
newArgvs.host = firstArg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "getQueryParam":
|
||||||
|
case "removeQueryParam":
|
||||||
|
newArgvs.url = firstArg;
|
||||||
|
newArgvs.param = secondArg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "addQueryParam":
|
||||||
|
newArgvs.url = firstArg;
|
||||||
|
newArgvs.param = secondArg;
|
||||||
|
newArgvs.value = thirdArg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newArgvs;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("解析URL参数失败:", e);
|
||||||
|
return this.defaultArgvs;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateArgvs(key, value) {
|
||||||
|
this.argvs = {
|
||||||
|
...this.argvs,
|
||||||
|
[key]: value,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
updateUrlObject(key, value) {
|
||||||
|
this.updateArgvs("urlObject", {
|
||||||
|
...this.argvs.urlObject,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getSummary(argvs) {
|
||||||
|
const op = this.operations.find(
|
||||||
|
(op) => op.name === argvs.operation
|
||||||
|
)?.label;
|
||||||
|
return op + " " + argvs.url.value;
|
||||||
|
},
|
||||||
|
updateModelValue(argvs) {
|
||||||
|
this.$emit("update:modelValue", {
|
||||||
|
...this.modelValue,
|
||||||
|
summary: this.getSummary(argvs),
|
||||||
|
argvs,
|
||||||
|
code: this.generateCode(argvs),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.modelValue.argvs && !this.modelValue.code) {
|
||||||
|
this.updateModelValue(this.defaultArgvs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
"argvs.operation": {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document
|
||||||
|
.querySelector(`.operation-card[data-value="${newVal}"]`)
|
||||||
|
?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
inline: "nearest",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.url-editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-container {
|
||||||
|
min-height: 32px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 覆盖command-composer的样式 */
|
||||||
|
.command-composer .operation-card {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -7,9 +7,6 @@
|
|||||||
:key="op.name"
|
:key="op.name"
|
||||||
:class="['operation-card', { active: argvs.operation === op.name }]"
|
:class="['operation-card', { active: argvs.operation === op.name }]"
|
||||||
@click="updateArgvs('operation', op.name)"
|
@click="updateArgvs('operation', op.name)"
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="row items-center justify-center q-gutter-x-xs q-px-sm q-py-xs"
|
|
||||||
>
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
:name="op.icon"
|
:name="op.icon"
|
||||||
@ -19,7 +16,6 @@
|
|||||||
<div class="text-caption">{{ op.label }}</div>
|
<div class="text-caption">{{ op.label }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 操作配置 -->
|
<!-- 操作配置 -->
|
||||||
<div class="operation-options q-mt-sm" v-if="hasOptions">
|
<div class="operation-options q-mt-sm" v-if="hasOptions">
|
||||||
@ -200,17 +196,24 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
if (activeIndex === -1) return {};
|
if (activeIndex === -1) return {};
|
||||||
|
|
||||||
// 计算选项卡的宽度和间距
|
// 获取操作卡片容器的宽度
|
||||||
const cardWidth = 80; // 卡片宽度
|
const container = document.querySelector(".operation-cards");
|
||||||
const gap = 4; // 卡片间距
|
if (!container) return {};
|
||||||
|
|
||||||
|
const containerWidth = container.offsetWidth;
|
||||||
|
const cardCount = this.operations.length;
|
||||||
|
|
||||||
|
// 计算每个卡片的位置
|
||||||
|
const cardWidth = 100; // 卡片宽度
|
||||||
const pointerWidth = 12; // 尖角宽度
|
const pointerWidth = 12; // 尖角宽度
|
||||||
|
|
||||||
// 计算尖角的左偏移量:
|
// 计算卡片之间的间距
|
||||||
// 1. 计算到当前选中卡片的起始位置:(cardWidth + gap) * activeIndex
|
const totalGapWidth = containerWidth - cardWidth * cardCount;
|
||||||
// 2. 加上卡片的一半宽度:cardWidth / 2
|
const gapWidth = totalGapWidth / (cardCount - 1);
|
||||||
// 3. 减去尖角的一半宽度:pointerWidth / 2
|
|
||||||
|
// 计算当前选中卡片的中心位置
|
||||||
const leftOffset =
|
const leftOffset =
|
||||||
(cardWidth + gap) * activeIndex + cardWidth / 2 - pointerWidth / 2;
|
(cardWidth + gapWidth) * activeIndex + cardWidth / 2 - pointerWidth / 2;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
left: `${leftOffset}px`,
|
left: `${leftOffset}px`,
|
||||||
@ -303,29 +306,6 @@ export default defineComponent({
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operation-cards {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-card {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 4px;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-card:hover {
|
|
||||||
background: var(--q-primary-opacity-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-card.active {
|
|
||||||
border-color: var(--q-primary);
|
|
||||||
background: var(--q-primary-opacity-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-options {
|
.operation-options {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
@ -381,4 +361,15 @@ export default defineComponent({
|
|||||||
color: white;
|
color: white;
|
||||||
background: var(--q-primary);
|
background: var(--q-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 覆盖command-composer的样式 */
|
||||||
|
.command-composer .operation-cards {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .operation-card {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -7,9 +7,7 @@
|
|||||||
:key="op.name"
|
:key="op.name"
|
||||||
:class="['operation-card', { active: argvs.operation === op.name }]"
|
:class="['operation-card', { active: argvs.operation === op.name }]"
|
||||||
@click="updateArgvs('operation', op.name)"
|
@click="updateArgvs('operation', op.name)"
|
||||||
>
|
:data-value="op.name"
|
||||||
<div
|
|
||||||
class="row items-center justify-center q-gutter-x-xs q-px-sm q-py-xs"
|
|
||||||
>
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
:name="op.icon"
|
:name="op.icon"
|
||||||
@ -19,7 +17,6 @@
|
|||||||
<div class="text-caption">{{ op.label }}</div>
|
<div class="text-caption">{{ op.label }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 操作配置 -->
|
<!-- 操作配置 -->
|
||||||
<div class="operation-options q-mt-sm">
|
<div class="operation-options q-mt-sm">
|
||||||
@ -433,6 +430,22 @@ export default defineComponent({
|
|||||||
this.updateModelValue(this.defaultArgvs);
|
this.updateModelValue(this.defaultArgvs);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
"argvs.operation": {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document
|
||||||
|
.querySelector(`.operation-card[data-value="${newVal}"]`)
|
||||||
|
?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
inline: "nearest",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -442,30 +455,6 @@ export default defineComponent({
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operation-cards {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-card {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 4px;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-card:hover {
|
|
||||||
background: var(--q-primary-opacity-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-card.active {
|
|
||||||
border-color: var(--q-primary);
|
|
||||||
background: var(--q-primary-opacity-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-container {
|
.options-container {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
395
src/components/composer/ui/ArrayEditor.vue
Normal file
395
src/components/composer/ui/ArrayEditor.vue
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
<template>
|
||||||
|
<div class="array-editor">
|
||||||
|
<div v-for="(item, index) in items" :key="index" class="row items-center">
|
||||||
|
<!-- 如果传入options.keys,则生成多键对象
|
||||||
|
示例:
|
||||||
|
options: {
|
||||||
|
keys: ['name', 'age', 'email']
|
||||||
|
}
|
||||||
|
生成数据结构示例:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: { value: "张三", isString: true, __varInputVal__: true },
|
||||||
|
age: { value: "18", isString: false, __varInputVal__: true },
|
||||||
|
email: { value: "zhangsan@example.com", isString: true, __varInputVal__: true }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
-->
|
||||||
|
<template v-if="options?.keys">
|
||||||
|
<div
|
||||||
|
v-for="key in options.keys"
|
||||||
|
:key="key"
|
||||||
|
:class="['col', options.keys.length > 1 ? 'q-pr-sm' : '']"
|
||||||
|
>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="item[key]"
|
||||||
|
:label="key"
|
||||||
|
:icon="icon || 'code'"
|
||||||
|
@update:model-value="(val) => updateItemKeyValue(index, key, val)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="col">
|
||||||
|
<!-- 如果传入options.items,则生成下拉选择
|
||||||
|
示例:
|
||||||
|
options: {
|
||||||
|
items: ['选项1', '选项2', '选项3']
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
<template v-if="options?.items">
|
||||||
|
<q-select
|
||||||
|
:model-value="item.value"
|
||||||
|
:options="filterOptions"
|
||||||
|
:label="`${label || '项目'} ${index + 1}`"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
use-input
|
||||||
|
input-debounce="0"
|
||||||
|
:hide-selected="!!inputValue"
|
||||||
|
@filter="filterFn"
|
||||||
|
@update:model-value="(val) => handleSelect(val, index)"
|
||||||
|
@input-value="(val) => handleInput(val, index)"
|
||||||
|
@blur="handleBlur"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon :name="icon || 'code'" />
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</template>
|
||||||
|
<!-- 不传options情况下,生成单值对象
|
||||||
|
生成数据结构示例:
|
||||||
|
[
|
||||||
|
"张三",
|
||||||
|
"李四",
|
||||||
|
"王五"
|
||||||
|
]
|
||||||
|
-->
|
||||||
|
<template v-else>
|
||||||
|
<VariableInput
|
||||||
|
:model-value="item"
|
||||||
|
:label="`${label || '项目'} ${index + 1}`"
|
||||||
|
:icon="icon || 'code'"
|
||||||
|
@update:model-value="(val) => updateItemValue(index, val)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="btn-container">
|
||||||
|
<template v-if="items.length === 1">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
icon="add"
|
||||||
|
class="center-btn"
|
||||||
|
@click="addItem"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="index === items.length - 1">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
icon="remove"
|
||||||
|
class="top-btn"
|
||||||
|
@click="removeItem(index)"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
icon="add"
|
||||||
|
class="bottom-btn"
|
||||||
|
@click="addItem"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
icon="remove"
|
||||||
|
class="center-btn"
|
||||||
|
@click="removeItem(index)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import VariableInput from "components/composer/ui/VariableInput.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "ArrayEditor",
|
||||||
|
components: {
|
||||||
|
VariableInput,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 配置选项,支持两种模式:
|
||||||
|
* 1. 选项模式:通过 items 提供选项列表
|
||||||
|
* 数组的每个元素都可以从选项中选择值
|
||||||
|
*
|
||||||
|
* 2. 多键模式:通过 keys 定义每个数组元素包含的键
|
||||||
|
* 数组的每个元素都是一个对象,包含指定的键,每个键对应一个输入框
|
||||||
|
*/
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 本地维护的数组数据
|
||||||
|
localItems: this.initializeItems(),
|
||||||
|
// 选项模式下的过滤选项
|
||||||
|
filterOptions: this.options?.items || [],
|
||||||
|
// 选项模式下的输入值
|
||||||
|
inputValue: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
items: {
|
||||||
|
get() {
|
||||||
|
return this.localItems;
|
||||||
|
},
|
||||||
|
set(newItems) {
|
||||||
|
this.localItems = newItems;
|
||||||
|
this.$emit("update:modelValue", newItems);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 初始化数组项
|
||||||
|
* 1. 如果传入了初始值,直接使用
|
||||||
|
* 2. 如果配置了 keys,创建包含所有键的对象
|
||||||
|
* 3. 默认创建单值对象
|
||||||
|
*/
|
||||||
|
initializeItems() {
|
||||||
|
if (this.modelValue.length) {
|
||||||
|
return this.modelValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options?.keys) {
|
||||||
|
const item = {};
|
||||||
|
this.options.keys.forEach((key) => {
|
||||||
|
item[key] = {
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return [item];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 添加新的数组项
|
||||||
|
* 根据配置创建相应的数据结构
|
||||||
|
*/
|
||||||
|
addItem() {
|
||||||
|
if (this.options?.keys) {
|
||||||
|
const newItem = {};
|
||||||
|
this.options.keys.forEach((key) => {
|
||||||
|
newItem[key] = {
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.items = [...this.items, newItem];
|
||||||
|
} else {
|
||||||
|
this.items = [
|
||||||
|
...this.items,
|
||||||
|
{
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 移除指定索引的数组项
|
||||||
|
* 如果移除后数组为空,则创建一个新的空项
|
||||||
|
*/
|
||||||
|
removeItem(index) {
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems.splice(index, 1);
|
||||||
|
if (newItems.length === 0) {
|
||||||
|
if (this.options?.keys) {
|
||||||
|
const newItem = {};
|
||||||
|
this.options.keys.forEach((key) => {
|
||||||
|
newItem[key] = {
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
newItems.push(newItem);
|
||||||
|
} else {
|
||||||
|
newItems.push({
|
||||||
|
value: "",
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.items = newItems;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新单值模式下的值
|
||||||
|
*/
|
||||||
|
updateItemValue(index, value) {
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems[index] = value;
|
||||||
|
this.items = newItems;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新多键模式下指定键的值
|
||||||
|
*/
|
||||||
|
updateItemKeyValue(index, key, value) {
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems[index] = {
|
||||||
|
...newItems[index],
|
||||||
|
[key]: value,
|
||||||
|
};
|
||||||
|
this.items = newItems;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选项模式下的输入处理
|
||||||
|
* 当输入的值不在选项中时,创建新值
|
||||||
|
*/
|
||||||
|
handleInput(val, index) {
|
||||||
|
this.inputValue = val;
|
||||||
|
if (val && !this.filterOptions.includes(val)) {
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems[index] = {
|
||||||
|
value: val,
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
};
|
||||||
|
this.items = newItems;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选项模式下的选择处理
|
||||||
|
*/
|
||||||
|
handleSelect(val, index) {
|
||||||
|
this.inputValue = "";
|
||||||
|
const newItems = [...this.items];
|
||||||
|
newItems[index] = {
|
||||||
|
value: val,
|
||||||
|
isString: false,
|
||||||
|
__varInputVal__: true,
|
||||||
|
};
|
||||||
|
this.items = newItems;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选项模式下的失焦处理
|
||||||
|
*/
|
||||||
|
handleBlur() {
|
||||||
|
this.inputValue = "";
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选项模式下的过滤处理
|
||||||
|
* 根据输入值过滤可选项
|
||||||
|
*/
|
||||||
|
filterFn(val, update) {
|
||||||
|
if (!this.options?.items) return;
|
||||||
|
|
||||||
|
update(() => {
|
||||||
|
if (val === "") {
|
||||||
|
this.filterOptions = this.options.items;
|
||||||
|
} else {
|
||||||
|
const needle = val.toLowerCase();
|
||||||
|
this.filterOptions = this.options.items.filter(
|
||||||
|
(v) => v.toLowerCase().indexOf(needle) > -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.array-editor {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 防止输入框换行 */
|
||||||
|
:deep(.q-field__native) {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
position: relative;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container .q-btn {
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
min-height: 16px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container .center-btn {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container .top-btn {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container .bottom-btn {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-btn .q-icon) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-btn.q-btn--dense) {
|
||||||
|
padding: 0;
|
||||||
|
min-height: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,6 +6,12 @@
|
|||||||
class="row q-col-gutter-sm items-center"
|
class="row q-col-gutter-sm items-center"
|
||||||
>
|
>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
|
<!-- 如果传入options.items,则键值支持下拉选择
|
||||||
|
示例:
|
||||||
|
options: {
|
||||||
|
items: ['User-Agent', 'Content-Type', 'Accept']
|
||||||
|
}
|
||||||
|
-->
|
||||||
<q-select
|
<q-select
|
||||||
v-if="options?.items"
|
v-if="options?.items"
|
||||||
:model-value="item.key"
|
:model-value="item.key"
|
||||||
@ -25,6 +31,7 @@
|
|||||||
<q-icon name="code" />
|
<q-icon name="code" />
|
||||||
</template>
|
</template>
|
||||||
</q-select>
|
</q-select>
|
||||||
|
<!-- 不传options.items时,键值为非VariableInput的输入框 -->
|
||||||
<q-input
|
<q-input
|
||||||
v-else
|
v-else
|
||||||
:model-value="item.key"
|
:model-value="item.key"
|
||||||
@ -39,9 +46,10 @@
|
|||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
<!-- 值使用VariableInput组件 -->
|
||||||
<VariableInput
|
<VariableInput
|
||||||
:model-value="item.value"
|
:model-value="item.value"
|
||||||
:label="item.key || '值'"
|
label="值"
|
||||||
icon="code"
|
icon="code"
|
||||||
class="col-grow"
|
class="col-grow"
|
||||||
@update:model-value="(val) => updateItemValue(val, index)"
|
@update:model-value="(val) => updateItemValue(val, index)"
|
||||||
@ -107,6 +115,11 @@ export default defineComponent({
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 配置选项,支持:
|
||||||
|
* 选项模式:通过 items 提供选项列表
|
||||||
|
* 字典的每个键都可以从选项中选择值
|
||||||
|
*/
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
|
@ -5,20 +5,24 @@
|
|||||||
class="flex-item"
|
class="flex-item"
|
||||||
:style="{ flex: localCommand.functionSelector.width || 3 }"
|
:style="{ flex: localCommand.functionSelector.width || 3 }"
|
||||||
>
|
>
|
||||||
<q-select
|
<div class="operation-cards">
|
||||||
v-model="functionName"
|
<div
|
||||||
:options="localCommand.functionSelector.options"
|
v-for="option in localCommand.functionSelector?.options"
|
||||||
:label="localCommand.functionSelector.selectLabel"
|
:key="option.value"
|
||||||
dense
|
:class="['operation-card', { active: functionName === option.value }]"
|
||||||
filled
|
:data-value="option.value"
|
||||||
emit-value
|
@click="functionName = option.value"
|
||||||
map-options
|
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<q-icon
|
||||||
<q-icon :name="localCommand.icon || 'functions'" />
|
:name="option.icon || localCommand.icon || 'functions'"
|
||||||
</template>
|
size="16px"
|
||||||
</q-select>
|
:color="functionName === option.value ? 'primary' : 'grey'"
|
||||||
|
/>
|
||||||
|
<div class="text-caption">{{ option.label }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-container">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in localConfig"
|
v-for="(item, index) in localConfig"
|
||||||
:key="index"
|
:key="index"
|
||||||
@ -43,6 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -170,7 +175,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
getSummary(argvs) {
|
getSummary(argvs) {
|
||||||
// 虽然header里对溢出做了处理,但是这里截断主要是为了节省存储空间
|
// 虽然header里对溢出做了处理,但是这里截断主要是为了节省存储空间
|
||||||
return argvs
|
const funcNameLabel = this.localCommand.functionSelector?.options.find(
|
||||||
|
(option) => option.value === this.functionName
|
||||||
|
)?.label;
|
||||||
|
const subFeature = funcNameLabel ? `${funcNameLabel} ` : "";
|
||||||
|
const allArgvs = argvs
|
||||||
.map((item) =>
|
.map((item) =>
|
||||||
item?.hasOwnProperty("__varInputVal__")
|
item?.hasOwnProperty("__varInputVal__")
|
||||||
? window.lodashM.truncate(item.value, {
|
? window.lodashM.truncate(item.value, {
|
||||||
@ -179,8 +188,8 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
: item
|
: item
|
||||||
)
|
)
|
||||||
.filter((item) => item != null)
|
.filter((item) => item != null && item != "");
|
||||||
.join("、");
|
return `${subFeature}${allArgvs.join(",")}`;
|
||||||
},
|
},
|
||||||
updateModelValue(functionName, argvs) {
|
updateModelValue(functionName, argvs) {
|
||||||
this.$emit("update:modelValue", {
|
this.$emit("update:modelValue", {
|
||||||
@ -201,6 +210,23 @@ export default defineComponent({
|
|||||||
this.updateModelValue(this.functionName, this.defaultArgvs);
|
this.updateModelValue(this.functionName, this.defaultArgvs);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
functionName: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
// 当操作卡片改变时,确保它在视图中可见
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document
|
||||||
|
.querySelector(`.operation-card[data-value="${newVal}"]`)
|
||||||
|
?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
inline: "nearest",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -213,12 +239,56 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-item {
|
.flex-item {
|
||||||
min-width: 100px; /* 设置最小宽度以确保内容可读性 */
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.flex-item {
|
.flex-item {
|
||||||
flex: 1 1 100% !important; /* 在小屏幕上强制换行 */
|
flex: 1 1 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.operation-cards {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 1px;
|
||||||
|
gap: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-cards::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-card {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-width: 72px;
|
||||||
|
padding: 2px 0;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .operation-card {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-card:hover {
|
||||||
|
background: var(--q-primary-opacity-5);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
border: 1px solid var(--q-primary-opacity-10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-card.active {
|
||||||
|
border-color: var(--q-primary);
|
||||||
|
background: var(--q-primary-opacity-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .operation-card.active {
|
||||||
|
border-color: var(--q-primary-opacity-50);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
146
src/css/composer.css
Normal file
146
src/css/composer.css
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/* 操作卡片样式 */
|
||||||
|
.command-composer .operation-cards {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 1px;
|
||||||
|
gap: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .operation-cards::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .operation-card {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-width: 72px;
|
||||||
|
max-height: 36px;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .operation-card:hover {
|
||||||
|
background: var(--q-primary-opacity-5);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
border: 1px solid var(--q-primary-opacity-10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .operation-card.active {
|
||||||
|
border-color: var(--q-primary);
|
||||||
|
background: var(--q-primary-opacity-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色模式适配 */
|
||||||
|
.body--dark .command-composer .operation-card {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .command-composer .operation-card.active {
|
||||||
|
border-color: var(--q-primary-opacity-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动美化 */
|
||||||
|
.command-composer .q-scrollarea__thumb {
|
||||||
|
width: 2px;
|
||||||
|
opacity: 0.4;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .q-scrollarea__thumb:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 布局更加紧凑 */
|
||||||
|
/* 输入框高度及字体 */
|
||||||
|
.command-composer .q-field--filled:not(.q-textarea) .q-field__control,
|
||||||
|
.command-composer .q-field--filled:not(.q-textarea) .q-field__control>*,
|
||||||
|
.command-composer .q-field--filled:not(.q-field--labeled):not(.q-textarea) .q-field__native {
|
||||||
|
max-height: 36px !important;
|
||||||
|
min-height: 36px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .q-field--filled .q-field__control,
|
||||||
|
.command-composer .q-field--filled .q-field__control>*,
|
||||||
|
.command-composer .q-field--filled .q-field__native {
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框图标大小 */
|
||||||
|
.command-composer .q-field--filled .q-field__control .q-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框标签字体大小,占位时的位置 */
|
||||||
|
.command-composer .q-field--filled .q-field__label {
|
||||||
|
font-size: 11px;
|
||||||
|
top: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框标签悬浮的位置 */
|
||||||
|
.command-composer .q-field--filled .q-field--float .q_field__label {
|
||||||
|
transform: translateY(-35%) scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 去除filled输入框边框 */
|
||||||
|
.command-composer .q-field--filled .q-field__control:before {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 去除filled输入框下划线 */
|
||||||
|
.command-composer .q-field--filled .q-field__control:after {
|
||||||
|
height: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框背景颜色及内边距 */
|
||||||
|
.command-composer .q-field--filled .q-field__control {
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框聚焦时的背景颜色 */
|
||||||
|
.command-composer .q-field--filled.q-field--highlighted .q-field__control {
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗黑模式下的输入框背景颜色 */
|
||||||
|
.body--dark .command-composer .q-field--filled .q-field__control {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗黑模式下输入框聚焦时的背景颜色 */
|
||||||
|
.body--dark .command-composer .q-field--filled.q-field--highlighted .q-field__control {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* checkbox/toggle大小及字体 */
|
||||||
|
.command-composer .q-checkbox__label,
|
||||||
|
.command-composer .q-toggle__label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-composer .q-checkbox__inner,
|
||||||
|
.command-composer .q-toggle__inner {
|
||||||
|
font-size: 28px;
|
||||||
|
margin: 4px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗黑模式下的标签栏背景颜色 */
|
||||||
|
.body--dark .command-composer .q-tab,
|
||||||
|
.body--dark .command-composer .q-tab-panel {
|
||||||
|
background-color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .command-composer .q-tab--inactive {
|
||||||
|
opacity: 2;
|
||||||
|
}
|
@ -65,5 +65,17 @@ export const OsEditor = defineAsyncComponent(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const PathEditor = defineAsyncComponent(() =>
|
export const PathEditor = defineAsyncComponent(() =>
|
||||||
import("src/components/composer/system/PathEditor.vue")
|
import("components/composer/system/PathEditor.vue")
|
||||||
|
);
|
||||||
|
export const ZlibEditor = defineAsyncComponent(() =>
|
||||||
|
import("components/composer/file/ZlibEditor.vue")
|
||||||
|
);
|
||||||
|
export const UrlEditor = defineAsyncComponent(() =>
|
||||||
|
import("components/composer/network/UrlEditor.vue")
|
||||||
|
);
|
||||||
|
export const DnsEditor = defineAsyncComponent(() =>
|
||||||
|
import("components/composer/network/DnsEditor.vue")
|
||||||
|
);
|
||||||
|
export const BufferEditor = defineAsyncComponent(() =>
|
||||||
|
import("components/composer/developer/BufferEditor.vue")
|
||||||
);
|
);
|
||||||
|
14
src/js/composer/commands/developerCommands.js
Normal file
14
src/js/composer/commands/developerCommands.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const developerCommands = {
|
||||||
|
label: "开发相关",
|
||||||
|
icon: "code",
|
||||||
|
defaultOpened: true,
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
value: "quickcomposer.developer.buffer",
|
||||||
|
label: "Buffer操作",
|
||||||
|
desc: "Buffer创建、转换和操作",
|
||||||
|
component: "BufferEditor",
|
||||||
|
icon: "memory",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
@ -46,5 +46,13 @@ export const fileCommands = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "quickcomposer.file.zlib",
|
||||||
|
label: "压缩解压",
|
||||||
|
desc: "使用 zlib 进行数据压缩和解压",
|
||||||
|
component: "ZlibEditor",
|
||||||
|
icon: "compress",
|
||||||
|
isAsync: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import { textCommands } from "./textCommands";
|
|||||||
import { otherCommands } from "./otherCommands";
|
import { otherCommands } from "./otherCommands";
|
||||||
import { simulateCommands } from "./simulateCommands";
|
import { simulateCommands } from "./simulateCommands";
|
||||||
import { controlCommands } from "./controlCommands";
|
import { controlCommands } from "./controlCommands";
|
||||||
|
import { developerCommands } from "./developerCommands";
|
||||||
|
|
||||||
export const commandCategories = [
|
export const commandCategories = [
|
||||||
fileCommands,
|
fileCommands,
|
||||||
@ -16,4 +17,5 @@ export const commandCategories = [
|
|||||||
controlCommands,
|
controlCommands,
|
||||||
otherCommands,
|
otherCommands,
|
||||||
simulateCommands,
|
simulateCommands,
|
||||||
|
developerCommands,
|
||||||
];
|
];
|
||||||
|
@ -44,5 +44,20 @@ export const networkCommands = {
|
|||||||
isAsync: true,
|
isAsync: true,
|
||||||
icon: "http",
|
icon: "http",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "quickcomposer.network.url",
|
||||||
|
label: "URL操作",
|
||||||
|
desc: "URL解析、格式化和参数处理",
|
||||||
|
component: "UrlEditor",
|
||||||
|
icon: "link",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "quickcomposer.network.dns",
|
||||||
|
label: "DNS操作",
|
||||||
|
desc: "DNS解析和查询",
|
||||||
|
component: "DnsEditor",
|
||||||
|
icon: "dns",
|
||||||
|
isAsync: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -22,28 +22,42 @@ export const textCommands = {
|
|||||||
{
|
{
|
||||||
label: "Base64编码",
|
label: "Base64编码",
|
||||||
value: "quickcomposer.text.base64Encode",
|
value: "quickcomposer.text.base64Encode",
|
||||||
|
icon: "title",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Base64解码",
|
label: "Base64解码",
|
||||||
value: "quickcomposer.text.base64Decode",
|
value: "quickcomposer.text.base64Decode",
|
||||||
|
icon: "title",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "十六进制编码",
|
label: "十六进制编码",
|
||||||
value: "quickcomposer.text.hexEncode",
|
value: "quickcomposer.text.hexEncode",
|
||||||
|
icon: "code",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "十六进制解码",
|
label: "十六进制解码",
|
||||||
value: "quickcomposer.text.hexDecode",
|
value: "quickcomposer.text.hexDecode",
|
||||||
|
icon: "code",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "URL编码",
|
||||||
|
value: "quickcomposer.text.urlEncode",
|
||||||
|
icon: "link",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "URL解码",
|
||||||
|
value: "quickcomposer.text.urlDecode",
|
||||||
|
icon: "link",
|
||||||
},
|
},
|
||||||
{ label: "URL编码", value: "quickcomposer.text.urlEncode" },
|
|
||||||
{ label: "URL解码", value: "quickcomposer.text.urlDecode" },
|
|
||||||
{
|
{
|
||||||
label: "HTML编码",
|
label: "HTML编码",
|
||||||
value: "quickcomposer.text.htmlEncode",
|
value: "quickcomposer.text.htmlEncode",
|
||||||
|
icon: "html",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "HTML解码",
|
label: "HTML解码",
|
||||||
value: "quickcomposer.text.htmlDecode",
|
value: "quickcomposer.text.htmlDecode",
|
||||||
|
icon: "html",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
width: 3,
|
width: 3,
|
||||||
@ -75,11 +89,31 @@ export const textCommands = {
|
|||||||
functionSelector: {
|
functionSelector: {
|
||||||
selectLabel: "哈希算法",
|
selectLabel: "哈希算法",
|
||||||
options: [
|
options: [
|
||||||
{ label: "MD5", value: "quickcomposer.text.md5Hash" },
|
{
|
||||||
{ label: "SHA1", value: "quickcomposer.text.sha1Hash" },
|
label: "MD5",
|
||||||
{ label: "SHA256", value: "quickcomposer.text.sha256Hash" },
|
value: "quickcomposer.text.md5Hash",
|
||||||
{ label: "SHA512", value: "quickcomposer.text.sha512Hash" },
|
icon: "functions",
|
||||||
{ label: "SM3", value: "quickcomposer.text.sm3Hash" },
|
},
|
||||||
|
{
|
||||||
|
label: "SHA1",
|
||||||
|
value: "quickcomposer.text.sha1Hash",
|
||||||
|
icon: "functions",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "SHA256",
|
||||||
|
value: "quickcomposer.text.sha256Hash",
|
||||||
|
icon: "functions",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "SHA512",
|
||||||
|
value: "quickcomposer.text.sha512Hash",
|
||||||
|
icon: "functions",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "SM3",
|
||||||
|
value: "quickcomposer.text.sm3Hash",
|
||||||
|
icon: "functions",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
width: 3,
|
width: 3,
|
||||||
|
@ -150,12 +150,16 @@ const isPathMatched = (path, patterns) => {
|
|||||||
const regexPattern = pattern
|
const regexPattern = pattern
|
||||||
// 先处理 **,将其转换为特殊标记
|
// 先处理 **,将其转换为特殊标记
|
||||||
.replace(/\*\*/g, "###DOUBLEWILDCARD###")
|
.replace(/\*\*/g, "###DOUBLEWILDCARD###")
|
||||||
|
// 处理数组索引通配符 [*]
|
||||||
|
.replace(/\[\*\]/g, "###ARRAYINDEX###")
|
||||||
// 处理普通的 *
|
// 处理普通的 *
|
||||||
.replace(/\*/g, "[^/.]+")
|
.replace(/\*/g, "[^/.\\[\\]]+")
|
||||||
// 转义特殊字符
|
// 转义特殊字符
|
||||||
.replace(/[.]/g, "\\$&")
|
.replace(/[.[\]]/g, "\\$&")
|
||||||
// 还原 ** 为正则表达式
|
// 还原 ** 为正则表达式
|
||||||
.replace(/###DOUBLEWILDCARD###/g, ".*");
|
.replace(/###DOUBLEWILDCARD###/g, ".*")
|
||||||
|
// 还原数组索引通配符
|
||||||
|
.replace(/###ARRAYINDEX###/g, "\\[\\d+\\]");
|
||||||
|
|
||||||
const regex = new RegExp(`^${regexPattern}$`);
|
const regex = new RegExp(`^${regexPattern}$`);
|
||||||
return regex.test(path);
|
return regex.test(path);
|
||||||
@ -188,25 +192,28 @@ const isPathMatched = (path, patterns) => {
|
|||||||
* - arg1.data.* - 匹配data下的所有直接子属性
|
* - arg1.data.* - 匹配data下的所有直接子属性
|
||||||
* - arg2.params.** - 匹配params下的所有属性(包括嵌套)
|
* - arg2.params.** - 匹配params下的所有属性(包括嵌套)
|
||||||
*
|
*
|
||||||
* 3. 通配符:
|
* 3. 数组索引:
|
||||||
* - * - 匹配单个层级的任意字符(不包含点号)
|
* - arg0[0] - 匹配数组的第一个元素
|
||||||
* - ** - 匹配任意层级(包含点号)
|
* - arg0[*] - 匹配数组的任意元素
|
||||||
|
* - arg0[*].name - 匹配数组任意元素的name属性
|
||||||
|
* - arg0[*].** - 匹配数组任意元素的所有属性(包括嵌套)
|
||||||
*
|
*
|
||||||
* 4. 排除规则:
|
* 4. 通配符:
|
||||||
|
* - * - 匹配单个层级的任意字符(不包含点号和方括号)
|
||||||
|
* - ** - 匹配任意层级(包含点号)
|
||||||
|
* - [*] - 匹配任意数组索引
|
||||||
|
*
|
||||||
|
* 5. 排除规则:
|
||||||
* - !pattern - 排除匹配的路径
|
* - !pattern - 排除匹配的路径
|
||||||
* - 排除优先级高于包含
|
* - 排除优先级高于包含
|
||||||
*
|
*
|
||||||
* 5. 示例:
|
* 6. 示例:
|
||||||
* - arg0 - 匹配第一个参数
|
* - arg0 - 匹配第一个参数
|
||||||
* - arg*.headers.** - 匹配任意参数中headers下的所有属性
|
* - arg*.headers.** - 匹配任意参数中headers下的所有属性
|
||||||
* - arg*.data.* - 匹配任意参数中data下的直接子属性
|
* - arg*.data.* - 匹配任意参数中data下的直接子属性
|
||||||
|
* - arg0[*] - 匹配第一个参数的所有数组元素
|
||||||
|
* - arg0[*].name - 匹配第一个参数数组中所有元素的name属性
|
||||||
* - !arg*.headers.Content-Type - 排除所有参数中的Content-Type头
|
* - !arg*.headers.Content-Type - 排除所有参数中的Content-Type头
|
||||||
* - arg*.headers.Accept* - 匹配所有以Accept开头的头部
|
|
||||||
*
|
|
||||||
* 6. 使用建议:
|
|
||||||
* - 优先使用精确匹配(arg0, arg1.data)
|
|
||||||
* - 使用通配符时注意层级(* vs **)
|
|
||||||
* - 合理使用排除规则避免过度匹配
|
|
||||||
*
|
*
|
||||||
* @returns {Object} 解析结果,包含函数名和参数数组
|
* @returns {Object} 解析结果,包含函数名和参数数组
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user