mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-29 20:32:44 +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"),
|
||||
file: require("./quickcomposer/file"),
|
||||
system: require("./quickcomposer/system"),
|
||||
network: require("./quickcomposer/network"),
|
||||
developer: require("./quickcomposer/developer"),
|
||||
};
|
||||
|
||||
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 zlib = require("./zlib");
|
||||
|
||||
module.exports = {
|
||||
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: [],
|
||||
|
||||
// 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
|
||||
extras: [
|
||||
@ -98,6 +98,7 @@ module.exports = configure(function (ctx) {
|
||||
path.join(__dirname, "./src/plugins")
|
||||
);
|
||||
chain.resolve.alias.set("js", path.join(__dirname, "./src/js"));
|
||||
chain.resolve.alias.set("css", path.join(__dirname, "./src/css"));
|
||||
},
|
||||
extendWebpack(cfg) {
|
||||
cfg.optimization.splitChunks = {
|
||||
|
@ -90,7 +90,8 @@ export default defineComponent({
|
||||
if (type === "load") return this.loadFlow();
|
||||
const code = flow ? generateCode(flow) : generateCode(this.commandFlow);
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
/* 滚动美化 */
|
||||
:deep(.q-scrollarea__thumb) {
|
||||
width: 2px;
|
||||
opacity: 0.4;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
:deep(.q-scrollarea__thumb:hover) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
.command-section {
|
||||
transition: all 0.3s ease;
|
||||
@ -176,96 +166,4 @@ export default defineComponent({
|
||||
.body--dark .command-section:hover {
|
||||
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>
|
||||
|
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'">
|
||||
<div class="row q-gutter-sm">
|
||||
<q-select
|
||||
v-model="argvs.encoding"
|
||||
:model-value="argvs.encoding || 'Buffer'"
|
||||
:options="encodingOptions"
|
||||
label="编码"
|
||||
dense
|
||||
@ -392,6 +392,7 @@ const ENCODING_OPTIONS = [
|
||||
{ label: "GB2312", value: "gb2312" },
|
||||
{ label: "GBK", value: "gbk" },
|
||||
{ label: "GB18030", value: "gb18030" },
|
||||
{ label: "Buffer", value: null },
|
||||
{ label: "Big5", value: "big5" },
|
||||
{ label: "ASCII", value: "ascii" },
|
||||
{ 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"
|
||||
:class="['operation-card', { active: argvs.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
|
||||
:name="op.icon"
|
||||
@ -19,7 +16,6 @@
|
||||
<div class="text-caption">{{ op.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作配置 -->
|
||||
<div class="operation-options q-mt-sm" v-if="hasOptions">
|
||||
@ -200,17 +196,24 @@ export default defineComponent({
|
||||
);
|
||||
if (activeIndex === -1) return {};
|
||||
|
||||
// 计算选项卡的宽度和间距
|
||||
const cardWidth = 80; // 卡片宽度
|
||||
const gap = 4; // 卡片间距
|
||||
// 获取操作卡片容器的宽度
|
||||
const container = document.querySelector(".operation-cards");
|
||||
if (!container) return {};
|
||||
|
||||
const containerWidth = container.offsetWidth;
|
||||
const cardCount = this.operations.length;
|
||||
|
||||
// 计算每个卡片的位置
|
||||
const cardWidth = 100; // 卡片宽度
|
||||
const pointerWidth = 12; // 尖角宽度
|
||||
|
||||
// 计算尖角的左偏移量:
|
||||
// 1. 计算到当前选中卡片的起始位置:(cardWidth + gap) * activeIndex
|
||||
// 2. 加上卡片的一半宽度:cardWidth / 2
|
||||
// 3. 减去尖角的一半宽度:pointerWidth / 2
|
||||
// 计算卡片之间的间距
|
||||
const totalGapWidth = containerWidth - cardWidth * cardCount;
|
||||
const gapWidth = totalGapWidth / (cardCount - 1);
|
||||
|
||||
// 计算当前选中卡片的中心位置
|
||||
const leftOffset =
|
||||
(cardWidth + gap) * activeIndex + cardWidth / 2 - pointerWidth / 2;
|
||||
(cardWidth + gapWidth) * activeIndex + cardWidth / 2 - pointerWidth / 2;
|
||||
|
||||
return {
|
||||
left: `${leftOffset}px`,
|
||||
@ -303,29 +306,6 @@ export default defineComponent({
|
||||
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 {
|
||||
position: relative;
|
||||
background: #f8f8f8;
|
||||
@ -381,4 +361,15 @@ export default defineComponent({
|
||||
color: white;
|
||||
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>
|
||||
|
@ -7,9 +7,7 @@
|
||||
:key="op.name"
|
||||
:class="['operation-card', { active: argvs.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"
|
||||
:data-value="op.name"
|
||||
>
|
||||
<q-icon
|
||||
:name="op.icon"
|
||||
@ -19,7 +17,6 @@
|
||||
<div class="text-caption">{{ op.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作配置 -->
|
||||
<div class="operation-options q-mt-sm">
|
||||
@ -433,6 +430,22 @@ export default defineComponent({
|
||||
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>
|
||||
|
||||
@ -442,30 +455,6 @@ export default defineComponent({
|
||||
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 {
|
||||
min-height: 32px;
|
||||
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"
|
||||
>
|
||||
<div class="col-4">
|
||||
<!-- 如果传入options.items,则键值支持下拉选择
|
||||
示例:
|
||||
options: {
|
||||
items: ['User-Agent', 'Content-Type', 'Accept']
|
||||
}
|
||||
-->
|
||||
<q-select
|
||||
v-if="options?.items"
|
||||
:model-value="item.key"
|
||||
@ -25,6 +31,7 @@
|
||||
<q-icon name="code" />
|
||||
</template>
|
||||
</q-select>
|
||||
<!-- 不传options.items时,键值为非VariableInput的输入框 -->
|
||||
<q-input
|
||||
v-else
|
||||
:model-value="item.key"
|
||||
@ -39,9 +46,10 @@
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col">
|
||||
<!-- 值使用VariableInput组件 -->
|
||||
<VariableInput
|
||||
:model-value="item.value"
|
||||
:label="item.key || '值'"
|
||||
label="值"
|
||||
icon="code"
|
||||
class="col-grow"
|
||||
@update:model-value="(val) => updateItemValue(val, index)"
|
||||
@ -107,6 +115,11 @@ export default defineComponent({
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* 配置选项,支持:
|
||||
* 选项模式:通过 items 提供选项列表
|
||||
* 字典的每个键都可以从选项中选择值
|
||||
*/
|
||||
options: {
|
||||
type: Object,
|
||||
default: null,
|
||||
|
@ -5,20 +5,24 @@
|
||||
class="flex-item"
|
||||
:style="{ flex: localCommand.functionSelector.width || 3 }"
|
||||
>
|
||||
<q-select
|
||||
v-model="functionName"
|
||||
:options="localCommand.functionSelector.options"
|
||||
:label="localCommand.functionSelector.selectLabel"
|
||||
dense
|
||||
filled
|
||||
emit-value
|
||||
map-options
|
||||
<div class="operation-cards">
|
||||
<div
|
||||
v-for="option in localCommand.functionSelector?.options"
|
||||
:key="option.value"
|
||||
:class="['operation-card', { active: functionName === option.value }]"
|
||||
:data-value="option.value"
|
||||
@click="functionName = option.value"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon :name="localCommand.icon || 'functions'" />
|
||||
</template>
|
||||
</q-select>
|
||||
<q-icon
|
||||
:name="option.icon || localCommand.icon || 'functions'"
|
||||
size="16px"
|
||||
:color="functionName === option.value ? 'primary' : 'grey'"
|
||||
/>
|
||||
<div class="text-caption">{{ option.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div
|
||||
v-for="(item, index) in localConfig"
|
||||
:key="index"
|
||||
@ -43,6 +47,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -170,7 +175,11 @@ export default defineComponent({
|
||||
},
|
||||
getSummary(argvs) {
|
||||
// 虽然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) =>
|
||||
item?.hasOwnProperty("__varInputVal__")
|
||||
? window.lodashM.truncate(item.value, {
|
||||
@ -179,8 +188,8 @@ export default defineComponent({
|
||||
})
|
||||
: item
|
||||
)
|
||||
.filter((item) => item != null)
|
||||
.join("、");
|
||||
.filter((item) => item != null && item != "");
|
||||
return `${subFeature}${allArgvs.join(",")}`;
|
||||
},
|
||||
updateModelValue(functionName, argvs) {
|
||||
this.$emit("update:modelValue", {
|
||||
@ -201,6 +210,23 @@ export default defineComponent({
|
||||
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>
|
||||
|
||||
@ -213,12 +239,56 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.flex-item {
|
||||
min-width: 100px; /* 设置最小宽度以确保内容可读性 */
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.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>
|
||||
|
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(() =>
|
||||
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 { simulateCommands } from "./simulateCommands";
|
||||
import { controlCommands } from "./controlCommands";
|
||||
import { developerCommands } from "./developerCommands";
|
||||
|
||||
export const commandCategories = [
|
||||
fileCommands,
|
||||
@ -16,4 +17,5 @@ export const commandCategories = [
|
||||
controlCommands,
|
||||
otherCommands,
|
||||
simulateCommands,
|
||||
developerCommands,
|
||||
];
|
||||
|
@ -44,5 +44,20 @@ export const networkCommands = {
|
||||
isAsync: true,
|
||||
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编码",
|
||||
value: "quickcomposer.text.base64Encode",
|
||||
icon: "title",
|
||||
},
|
||||
{
|
||||
label: "Base64解码",
|
||||
value: "quickcomposer.text.base64Decode",
|
||||
icon: "title",
|
||||
},
|
||||
{
|
||||
label: "十六进制编码",
|
||||
value: "quickcomposer.text.hexEncode",
|
||||
icon: "code",
|
||||
},
|
||||
{
|
||||
label: "十六进制解码",
|
||||
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编码",
|
||||
value: "quickcomposer.text.htmlEncode",
|
||||
icon: "html",
|
||||
},
|
||||
{
|
||||
label: "HTML解码",
|
||||
value: "quickcomposer.text.htmlDecode",
|
||||
icon: "html",
|
||||
},
|
||||
],
|
||||
width: 3,
|
||||
@ -75,11 +89,31 @@ export const textCommands = {
|
||||
functionSelector: {
|
||||
selectLabel: "哈希算法",
|
||||
options: [
|
||||
{ label: "MD5", value: "quickcomposer.text.md5Hash" },
|
||||
{ label: "SHA1", value: "quickcomposer.text.sha1Hash" },
|
||||
{ label: "SHA256", value: "quickcomposer.text.sha256Hash" },
|
||||
{ label: "SHA512", value: "quickcomposer.text.sha512Hash" },
|
||||
{ label: "SM3", value: "quickcomposer.text.sm3Hash" },
|
||||
{
|
||||
label: "MD5",
|
||||
value: "quickcomposer.text.md5Hash",
|
||||
icon: "functions",
|
||||
},
|
||||
{
|
||||
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,
|
||||
|
@ -150,12 +150,16 @@ const isPathMatched = (path, patterns) => {
|
||||
const regexPattern = pattern
|
||||
// 先处理 **,将其转换为特殊标记
|
||||
.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}$`);
|
||||
return regex.test(path);
|
||||
@ -188,25 +192,28 @@ const isPathMatched = (path, patterns) => {
|
||||
* - arg1.data.* - 匹配data下的所有直接子属性
|
||||
* - arg2.params.** - 匹配params下的所有属性(包括嵌套)
|
||||
*
|
||||
* 3. 通配符:
|
||||
* - * - 匹配单个层级的任意字符(不包含点号)
|
||||
* - ** - 匹配任意层级(包含点号)
|
||||
* 3. 数组索引:
|
||||
* - arg0[0] - 匹配数组的第一个元素
|
||||
* - arg0[*] - 匹配数组的任意元素
|
||||
* - arg0[*].name - 匹配数组任意元素的name属性
|
||||
* - arg0[*].** - 匹配数组任意元素的所有属性(包括嵌套)
|
||||
*
|
||||
* 4. 排除规则:
|
||||
* 4. 通配符:
|
||||
* - * - 匹配单个层级的任意字符(不包含点号和方括号)
|
||||
* - ** - 匹配任意层级(包含点号)
|
||||
* - [*] - 匹配任意数组索引
|
||||
*
|
||||
* 5. 排除规则:
|
||||
* - !pattern - 排除匹配的路径
|
||||
* - 排除优先级高于包含
|
||||
*
|
||||
* 5. 示例:
|
||||
* 6. 示例:
|
||||
* - arg0 - 匹配第一个参数
|
||||
* - arg*.headers.** - 匹配任意参数中headers下的所有属性
|
||||
* - arg*.data.* - 匹配任意参数中data下的直接子属性
|
||||
* - arg0[*] - 匹配第一个参数的所有数组元素
|
||||
* - arg0[*].name - 匹配第一个参数数组中所有元素的name属性
|
||||
* - !arg*.headers.Content-Type - 排除所有参数中的Content-Type头
|
||||
* - arg*.headers.Accept* - 匹配所有以Accept开头的头部
|
||||
*
|
||||
* 6. 使用建议:
|
||||
* - 优先使用精确匹配(arg0, arg1.data)
|
||||
* - 使用通配符时注意层级(* vs **)
|
||||
* - 合理使用排除规则避免过度匹配
|
||||
*
|
||||
* @returns {Object} 解析结果,包含函数名和参数数组
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user