OS/URL/PATH/BUFFER组件调整

This commit is contained in:
fofolee 2025-01-07 19:28:14 +08:00
parent 807da38e2e
commit e23d3e5e11
15 changed files with 902 additions and 2013 deletions

View File

@ -1,32 +1,57 @@
const url = require("url");
// URL 解析
function parse(urlString, parseQueryString = false) {
return url.parse(urlString, parseQueryString);
function parse(urlString) {
try {
return url.parse(urlString, false);
} catch (error) {
throw new Error(`URL解析失败: ${error.message}`);
}
}
// URL 格式化
function format(urlObject) {
return url.format(urlObject);
// 格式化 URL
function format(protocol, auth, hostname, port, pathname, search, hash) {
try {
const urlObject = {
protocol,
auth,
hostname,
port,
pathname,
search,
hash,
};
return url.format(urlObject);
} catch (error) {
throw new Error(`URL格式化失败: ${error.message}`);
}
}
// 解析查询字符串
function parseQuery(query) {
const searchParams = new URLSearchParams(query);
const result = {};
for (const [key, value] of searchParams) {
result[key] = value;
function parseQuery(queryString) {
try {
const searchParams = new URLSearchParams(queryString);
const result = {};
for (const [key, value] of searchParams) {
result[key] = value;
}
return result;
} catch (error) {
throw new Error(`查询字符串解析失败: ${error.message}`);
}
return result;
}
// 格式化查询字符串
function formatQuery(queryObject) {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(queryObject)) {
searchParams.append(key, value);
function formatQuery(queryParams) {
try {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(queryParams)) {
searchParams.append(key, value);
}
return searchParams.toString();
} catch (error) {
throw new Error(`查询字符串格式化失败: ${error.message}`);
}
return searchParams.toString();
}
// 解析路径名

View File

@ -6,7 +6,7 @@ function arch() {
}
// 获取CPU信息
function cpus({ format = "full" } = {}) {
function cpus(format = "full") {
const cpuInfo = os.cpus();
if (format === "simple") {
return cpuInfo.map(({ model, speed }) => ({ model, speed }));
@ -15,7 +15,7 @@ function cpus({ format = "full" } = {}) {
}
// 获取内存信息
function memory({ type = "totalmem" } = {}) {
function memory(type = "totalmem") {
switch (type) {
case "totalmem":
return os.totalmem();
@ -27,7 +27,7 @@ function memory({ type = "totalmem" } = {}) {
}
// 获取网络信息
function network({ type = "hostname", internal = false } = {}) {
function network(type = "hostname", internal = false) {
switch (type) {
case "hostname":
return os.hostname();
@ -50,7 +50,7 @@ function network({ type = "hostname", internal = false } = {}) {
}
// 获取平台信息
function platform({ type = "platform" } = {}) {
function platform(type = "platform") {
switch (type) {
case "platform":
return os.platform();

View File

@ -5,7 +5,7 @@ const path = require("path");
* @param {string} p 要规范化的路径
* @returns {string} 规范化后的路径
*/
async function normalize(p) {
function normalize(p) {
try {
return path.normalize(p);
} catch (error) {
@ -18,7 +18,7 @@ async function normalize(p) {
* @param {...string} paths 路径片段
* @returns {string} 连接后的路径
*/
async function join(...paths) {
function join(...paths) {
try {
return path.join(...paths);
} catch (error) {
@ -31,7 +31,7 @@ async function join(...paths) {
* @param {string} p 要解析的路径
* @returns {Object} 解析结果包含 root, dir, base, ext, name
*/
async function parse(p) {
function parse(p) {
try {
return path.parse(p);
} catch (error) {
@ -44,7 +44,7 @@ async function parse(p) {
* @param {string} p 路径
* @returns {string} 目录名
*/
async function dirname(p) {
function dirname(p) {
try {
return path.dirname(p);
} catch (error) {
@ -58,7 +58,7 @@ async function dirname(p) {
* @param {string} [ext] 可选的扩展名如果提供则从结果中移除
* @returns {string} 文件名
*/
async function basename(p, ext) {
function basename(p, ext = undefined) {
try {
return path.basename(p, ext);
} catch (error) {
@ -71,7 +71,7 @@ async function basename(p, ext) {
* @param {string} p 路径
* @returns {string} 扩展名
*/
async function extname(p) {
function extname(p) {
try {
return path.extname(p);
} catch (error) {
@ -84,7 +84,7 @@ async function extname(p) {
* @param {string} p 路径
* @returns {boolean} 是否为绝对路径
*/
async function isAbsolute(p) {
function isAbsolute(p) {
try {
return path.isAbsolute(p);
} catch (error) {
@ -98,7 +98,7 @@ async function isAbsolute(p) {
* @param {string} to 目标路径
* @returns {string} 相对路径
*/
async function relative(from, to) {
function relative(from, to) {
try {
return path.relative(from, to);
} catch (error) {
@ -111,7 +111,7 @@ async function relative(from, to) {
* @param {...string} paths 路径片段
* @returns {string} 解析后的绝对路径
*/
async function resolve(...paths) {
function resolve(...paths) {
try {
return path.resolve(...paths);
} catch (error) {
@ -121,11 +121,16 @@ async function resolve(...paths) {
/**
* 格式化路径对象为路径字符串
* @param {Object} pathObject 路径对象包含 dir, root, base, name, ext
* @param {string} root 根路径
* @param {string} dir 目录
* @param {string} base 基本名称
* @param {string} name 文件名
* @param {string} ext 扩展名
* @returns {string} 格式化后的路径
*/
async function format(pathObject) {
function format(root, dir, base, name, ext) {
try {
const pathObject = { root, dir, base, name, ext };
return path.format(pathObject);
} catch (error) {
throw new Error(`格式化路径失败: ${error.message}`);

View File

@ -55,7 +55,11 @@
<MultiParams
v-else
v-model="localCommand"
:class="localCommand.config?.length ? 'col q-mt-sm' : 'col'"
:class="
localCommand.config?.length || localCommand.functionSelector
? 'col q-mt-sm'
: 'col'
"
/>
</div>
</div>

View File

@ -111,7 +111,6 @@ export default defineComponent({
this.updateModelValue(this.funcName, newArgvs);
},
generateCode(funcName, argvs) {
console.log("argvs", argvs);
/**
* 字符串模式stringfiy后null会变成'"null"', ''变成'""'
* 变量模式stringify后null变成'null', ''保持''

View File

@ -0,0 +1,70 @@
<template>
<div class="button-group">
<div
v-for="opt in options"
:key="opt.value"
:class="['button-item', { active: modelValue === opt.value }]"
@click="$emit('update:modelValue', opt.value)"
>
{{ opt.label }}
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "ButtonGroup",
props: {
modelValue: {
required: true,
},
options: {
type: Array,
required: true,
},
},
emits: ["update:modelValue"],
});
</script>
<style scoped>
.button-group {
display: flex;
width: 100%;
flex-wrap: wrap;
gap: 6px;
padding: 10px;
border-radius: 6px;
}
.button-item {
flex: 1;
min-width: 80px;
display: inline-flex;
align-items: center;
justify-content: center;
height: 26px;
padding: 0 12px;
font-size: 12px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: var(--q-primary-opacity-5);
border: 1px solid var(--q-primary-opacity-5);
color: var(--q-primary);
white-space: nowrap;
user-select: none;
}
.button-item:hover {
transform: translateY(-1px);
}
.button-item.active {
color: white;
background: var(--q-primary);
border-color: var(--q-primary);
}
</style>

View File

@ -72,6 +72,12 @@
:label="config.label"
:icon="config.icon"
/>
<ButtonGroup
v-else-if="config.type === 'buttonGroup'"
:model-value="values[index]"
@update:model-value="$emit('update', index, $event)"
:options="config.options"
/>
</div>
</div>
</template>
@ -82,6 +88,7 @@ import VariableInput from "./VariableInput.vue";
import NumberInput from "./NumberInput.vue";
import ArrayEditor from "./ArrayEditor.vue";
import DictEditor from "./DictEditor.vue";
import ButtonGroup from "./ButtonGroup.vue";
/**
* 参数输入组件
@ -101,6 +108,7 @@ export default defineComponent({
NumberInput,
ArrayEditor,
DictEditor,
ButtonGroup,
},
props: {
configs: {

View File

@ -1,695 +0,0 @@
<template>
<div class="buffer-editor">
<!-- 操作类型选择 -->
<OperationCard
:model-value="argvs.operation"
@update:model-value="(val) => updateArgvs('operation', val)"
:options="operations"
/>
<!-- 操作配置 -->
<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 { stringifyArgv, parseFunction } from "js/composer/formatString";
import VariableInput from "components/composer/common/VariableInput.vue";
import NumberInput from "components/composer/common/NumberInput.vue";
import ArrayEditor from "components/composer/common/ArrayEditor.vue";
import OperationCard from "components/composer/common/OperationCard.vue";
export default defineComponent({
name: "BufferEditor",
components: {
VariableInput,
NumberInput,
ArrayEditor,
OperationCard,
},
props: {
modelValue: {
type: Object,
required: true,
},
},
emits: ["update:modelValue"],
data() {
return {
operations: [
{ value: "from", label: "创建Buffer", icon: "add_box" },
{ value: "toString", label: "转换字符串", icon: "text_fields" },
{ value: "write", label: "写入数据", icon: "edit" },
{ value: "fill", label: "填充数据", icon: "format_color_fill" },
{ value: "copy", label: "复制数据", icon: "content_copy" },
{ value: "compare", label: "比较数据", icon: "compare" },
{ value: "concat", label: "连接Buffer", icon: "merge" },
{ value: "indexOf", label: "查找数据", icon: "search" },
{ value: "slice", label: "切片数据", icon: "content_cut" },
{ value: "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}(${stringifyArgv(
argvs.data
)}, "${argvs.encoding}")`;
case "toString":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.buffer
)}, "${argvs.encoding}", ${argvs.start}, ${argvs.end})`;
case "write":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.buffer
)}, ${stringifyArgv(argvs.string)}, ${argvs.offset}, ${
argvs.length
}, "${argvs.encoding}")`;
case "fill":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.buffer
)}, ${stringifyArgv(argvs.value)}, ${argvs.offset}, ${argvs.end}, "${
argvs.encoding
}")`;
case "copy":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.source
)}, ${stringifyArgv(argvs.target)}, ${argvs.targetStart}, ${
argvs.sourceStart
}, ${argvs.sourceEnd})`;
case "compare":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.buf1
)}, ${stringifyArgv(argvs.buf2)})`;
case "concat":
const buffersStr = argvs.buffers
.map((buf) => stringifyArgv(buf))
.join(", ");
return `${this.modelValue.value}.${argvs.operation}([${buffersStr}]${
argvs.totalLength !== undefined ? `, ${argvs.totalLength}` : ""
})`;
case "indexOf":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.buffer
)}, ${stringifyArgv(argvs.value)}, ${argvs.byteOffset}, "${
argvs.encoding
}")`;
case "slice":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.buffer
)}, ${argvs.start}, ${argvs.end})`;
case "swap":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
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.argvs;
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.value === 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);
}
},
});
</script>
<style scoped>
.buffer-editor {
display: flex;
flex-direction: column;
}
.options-container {
min-height: 32px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

View File

@ -1,455 +0,0 @@
<template>
<div class="url-editor">
<!-- 操作类型选择 -->
<OperationCard
:model-value="argvs.operation"
@update:model-value="(val) => updateArgvs('operation', val)"
:options="operations"
/>
<!-- 操作配置 -->
<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 { stringifyArgv, parseFunction } from "js/composer/formatString";
import VariableInput from "components/composer/common/VariableInput.vue";
import DictEditor from "components/composer/common/DictEditor.vue";
import OperationCard from "components/composer/common/OperationCard.vue";
export default defineComponent({
name: "UrlEditor",
components: {
VariableInput,
DictEditor,
OperationCard,
},
props: {
modelValue: {
type: Object,
required: true,
},
},
emits: ["update:modelValue"],
data() {
return {
operations: [
{ value: "parse", label: "解析URL", icon: "link_off" },
{ value: "format", label: "格式化URL", icon: "link" },
{ value: "parseQuery", label: "解析查询字符串", icon: "search" },
{ value: "formatQuery", label: "格式化查询字符串", icon: "edit" },
{ value: "parsePath", label: "解析路径", icon: "folder_open" },
{ value: "parseHost", label: "解析主机名", icon: "dns" },
{ value: "getQueryParam", label: "获取参数", icon: "find_in_page" },
{ value: "addQueryParam", label: "添加参数", icon: "add_circle" },
{ value: "removeQueryParam", label: "移除参数", icon: "remove_circle" },
{ value: "isAbsolute", label: "检查绝对URL", icon: "check_circle" },
{ value: "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}(${stringifyArgv(
argvs.url
)})`;
case "format":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.urlObject
)})`;
case "parseQuery":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.query
)})`;
case "formatQuery":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.queryParams
)})`;
case "parsePath":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.path
)})`;
case "parseHost":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.host
)})`;
case "getQueryParam":
case "removeQueryParam":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.url
)}, ${stringifyArgv(argvs.param)})`;
case "addQueryParam":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.url
)}, ${stringifyArgv(argvs.param)}, ${stringifyArgv(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.argvs;
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.value === 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);
}
},
});
</script>
<style scoped>
.url-editor {
display: flex;
flex-direction: column;
}
.options-container {
min-height: 32px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

View File

@ -1,368 +0,0 @@
<template>
<div class="os-editor">
<!-- 操作类型选择 -->
<OperationCard
:model-value="argvs.operation"
@update:model-value="(val) => updateArgvs('operation', val)"
:options="operations"
/>
<!-- 操作配置 -->
<div class="operation-options q-mt-sm" v-if="hasOptions">
<div class="bubble-pointer" :style="pointerStyle"></div>
<!-- CPU信息配置 -->
<div v-if="argvs.operation === 'cpus'" class="options-container">
<div class="row items-center q-gutter-x-sm">
<div
v-for="opt in formatOptions"
:key="opt.value"
:class="['custom-btn', { active: argvs.format === opt.value }]"
@click="updateArgvs('format', opt.value)"
>
{{ opt.label }}
</div>
</div>
</div>
<!-- 内存信息配置 -->
<div v-if="argvs.operation === 'memory'" class="options-container">
<div class="row items-center q-gutter-x-sm">
<div
v-for="opt in memoryOptions"
:key="opt.value"
:class="['custom-btn', { active: argvs.type === opt.value }]"
@click="updateArgvs('type', opt.value)"
>
{{ opt.label }}
</div>
</div>
</div>
<!-- 网络信息配置 -->
<div v-if="argvs.operation === 'network'" class="options-container">
<div class="row items-center q-gutter-x-sm">
<div
v-for="opt in networkOptions"
:key="opt.value"
:class="['custom-btn', { active: argvs.type === opt.value }]"
@click="updateArgvs('type', opt.value)"
>
{{ opt.label }}
</div>
</div>
<div class="q-mt-xs" v-if="argvs.type === 'networkInterfaces'">
<q-checkbox
:model-value="argvs.internal"
@update:model-value="(val) => updateArgvs('internal', val)"
label="包含内部接口"
dense
class="text-caption"
/>
</div>
</div>
<!-- 平台信息配置 -->
<div v-if="argvs.operation === 'platform'" class="options-container">
<div class="row items-center q-gutter-x-sm wrap">
<div
v-for="opt in platformOptions"
:key="opt.value"
:class="['custom-btn', { active: argvs.type === opt.value }]"
@click="updateArgvs('type', opt.value)"
>
{{ opt.label }}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import { stringifyArgv, parseFunction } from "js/composer/formatString";
import OperationCard from "components/composer/common/OperationCard.vue";
export default defineComponent({
name: "OsEditor",
components: {
OperationCard,
},
props: {
modelValue: {
type: Object,
required: true,
},
},
emits: ["update:modelValue"],
data() {
return {
operations: [
{ value: "arch", label: "系统架构", icon: "memory" },
{ value: "cpus", label: "CPU信息", icon: "developer_board" },
{ value: "memory", label: "内存信息", icon: "storage" },
{ value: "network", label: "网络信息", icon: "wifi" },
{ value: "platform", label: "平台信息", icon: "computer" },
],
formatOptions: [
{ label: "完整信息", value: "full" },
{ label: "仅型号和速度", value: "simple" },
],
memoryOptions: [
{ label: "总内存", value: "totalmem" },
{ label: "空闲内存", value: "freemem" },
],
networkOptions: [
{ label: "主机名", value: "hostname" },
{ label: "网络接口", value: "networkInterfaces" },
],
platformOptions: [
{ label: "操作系统名称", value: "platform" },
{ label: "操作系统类型", value: "type" },
{ label: "操作系统版本", value: "release" },
{ label: "操作系统架构", value: "arch" },
{ label: "CPU字节序", value: "endianness" },
{ label: "系统临时目录", value: "tmpdir" },
{ label: "主目录", value: "homedir" },
{ label: "系统正常运行时间", value: "uptime" },
{ label: "用户信息", value: "userInfo" },
],
defaultArgvs: {
operation: "arch",
format: "full",
type: "platform",
internal: false,
},
};
},
computed: {
argvs: {
get() {
return (
this.modelValue.argvs ||
this.parseCodeToArgvs(this.modelValue.code) || {
...this.defaultArgvs,
}
);
},
set(value) {
//
const newValue = { ...value };
if (value.operation !== this.argvs.operation) {
switch (value.operation) {
case "cpus":
newValue.format = "full";
delete newValue.type;
delete newValue.internal;
break;
case "memory":
newValue.type = "totalmem";
delete newValue.format;
delete newValue.internal;
break;
case "network":
newValue.type = "hostname";
delete newValue.format;
break;
case "platform":
newValue.type = "platform";
delete newValue.format;
delete newValue.internal;
break;
default:
delete newValue.format;
delete newValue.type;
delete newValue.internal;
}
}
this.updateModelValue(newValue);
},
},
hasOptions() {
return ["cpus", "memory", "network", "platform"].includes(
this.argvs.operation
);
},
pointerStyle() {
const activeIndex = this.operations.findIndex(
(op) => op.value === this.argvs.operation
);
if (activeIndex === -1) return {};
//
const container = document.querySelector(".operation-cards");
if (!container) return {};
const containerWidth = container.offsetWidth;
const cardCount = this.operations.length;
//
const cardWidth = 100; //
const pointerWidth = 12; //
//
const totalGapWidth = containerWidth - cardWidth * cardCount;
const gapWidth = totalGapWidth / (cardCount - 1);
//
const leftOffset =
(cardWidth + gapWidth) * activeIndex + cardWidth / 2 - pointerWidth / 2;
return {
left: `${leftOffset}px`,
};
},
},
methods: {
generateCode(argvs = this.argvs) {
const params = {};
//
switch (argvs.operation) {
case "cpus":
params.format = argvs.format;
break;
case "memory":
params.type = argvs.type;
break;
case "network":
params.type = argvs.type;
if (argvs.type === "networkInterfaces") {
params.internal = argvs.internal;
}
break;
case "platform":
params.type = argvs.type;
break;
}
//
if (Object.keys(params).length === 0) {
return `${this.modelValue.value}.${argvs.operation}()`;
}
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
params
)})`;
},
parseCodeToArgvs(code) {
if (!code) return null;
try {
// 使 parseFunction
const result = parseFunction(code);
if (!result) return this.defaultArgvs;
//
const operation = result.name.split(".").pop();
const [params = {}] = result.argvs;
return {
...this.defaultArgvs,
operation,
...params,
};
} catch (e) {
console.error("解析OS参数失败:", e);
return this.defaultArgvs;
}
},
updateArgvs(key, value) {
this.argvs = {
...this.argvs,
[key]: value,
};
},
getSummary(argvs) {
return this.operations.find((op) => op.value === argvs.operation).label;
},
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>
.os-editor {
display: flex;
flex-direction: column;
}
.operation-options {
position: relative;
background: #f8f8f8;
border-radius: 4px;
padding: 12px;
margin-top: 12px !important;
z-index: 0;
}
.options-container {
min-height: 32px;
display: flex;
flex-direction: column;
justify-content: center;
}
.bubble-pointer {
position: absolute;
top: -6px;
width: 12px;
height: 12px;
background: #f8f8f8;
transform: rotate(45deg);
transition: left 0.3s ease;
z-index: 1;
}
.body--dark .operation-options,
.body--dark .bubble-pointer {
background: rgba(0, 0, 0, 0.1);
}
.custom-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
padding: 0 8px;
font-size: 12px;
border-radius: 3px;
cursor: pointer;
transition: all 0.2s ease;
color: var(--q-primary);
background: transparent;
white-space: nowrap;
}
.custom-btn:hover {
background: var(--q-primary-opacity-1);
}
.custom-btn.active {
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>

View File

@ -1,438 +0,0 @@
<template>
<div class="path-editor">
<!-- 操作类型选择 -->
<OperationCard
:model-value="argvs.operation"
@update:model-value="(val) => updateArgvs('operation', val)"
:options="operations"
/>
<!-- 操作配置 -->
<div class="operation-options q-mt-sm">
<!-- 通用路径输入 -->
<div class="options-container">
<template
v-if="
[
'normalize',
'parse',
'dirname',
'basename',
'extname',
'isAbsolute',
].includes(argvs.operation)
"
>
<VariableInput
:model-value="argvs.path"
@update:model-value="(val) => updateArgvs('path', val)"
label="路径"
icon="folder"
/>
<!-- basename 的扩展名参数 -->
<div v-if="argvs.operation === 'basename'" class="q-mt-sm">
<VariableInput
:model-value="argvs.ext"
@update:model-value="(val) => updateArgvs('ext', val)"
label="要移除的扩展名(可选)"
icon="extension"
/>
</div>
</template>
<!-- join resolve 的多路径输入 -->
<template v-if="['join', 'resolve'].includes(argvs.operation)">
<div
v-for="(path, index) in argvs.paths"
:key="index"
class="q-mb-sm"
>
<div class="row items-center q-gutter-sm">
<div class="col">
<VariableInput
:model-value="path"
@update:model-value="(val) => updatePathAtIndex(index, val)"
:label="'路径片段 ' + (index + 1)"
icon="folder"
/>
</div>
<q-btn
v-if="index === argvs.paths.length - 1"
flat
round
dense
icon="add"
size="sm"
color="primary"
@click="addPath"
/>
<q-btn
v-if="argvs.paths.length > 1"
flat
round
dense
icon="remove"
color="negative"
size="sm"
@click="removePath(index)"
/>
</div>
</div>
</template>
<!-- relative 的起始和目标路径 -->
<template v-if="argvs.operation === 'relative'">
<VariableInput
:model-value="argvs.from"
@update:model-value="(val) => updateArgvs('from', val)"
label="起始路径"
icon="folder"
/>
<div class="q-mt-sm">
<VariableInput
:model-value="argvs.to"
@update:model-value="(val) => updateArgvs('to', val)"
label="目标路径"
icon="folder"
/>
</div>
</template>
<!-- format 的路径对象 -->
<template v-if="argvs.operation === 'format'">
<div class="row q-col-gutter-sm">
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.pathObject.root"
@update:model-value="(val) => updatePathObject('root', val)"
label="根路径"
icon="folder"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.pathObject.dir"
@update:model-value="(val) => updatePathObject('dir', val)"
label="目录"
icon="folder"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.pathObject.base"
@update:model-value="(val) => updatePathObject('base', val)"
label="基本名称"
icon="description"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.pathObject.name"
@update:model-value="(val) => updatePathObject('name', val)"
label="文件名"
icon="insert_drive_file"
/>
</div>
<div class="col-12 col-sm-6">
<VariableInput
:model-value="argvs.pathObject.ext"
@update:model-value="(val) => updatePathObject('ext', val)"
label="扩展名"
icon="extension"
/>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import { stringifyArgv, parseFunction } from "js/composer/formatString";
import VariableInput from "components/composer/common/VariableInput.vue";
import OperationCard from "components/composer/common/OperationCard.vue";
export default defineComponent({
name: "PathEditor",
components: {
VariableInput,
OperationCard,
},
props: {
modelValue: {
type: Object,
required: true,
},
},
emits: ["update:modelValue"],
data() {
return {
operations: [
{ value: "normalize", label: "规范化路径", icon: "straighten" },
{ value: "join", label: "连接路径", icon: "add_link" },
{ value: "parse", label: "解析路径", icon: "account_tree" },
{ value: "dirname", label: "获取目录名", icon: "folder" },
{ value: "basename", label: "获取文件名", icon: "description" },
{ value: "extname", label: "获取扩展名", icon: "extension" },
{ value: "isAbsolute", label: "判断绝对路径", icon: "check_circle" },
{ value: "relative", label: "计算相对路径", icon: "compare_arrows" },
{ value: "resolve", label: "解析绝对路径", icon: "assistant_direction" },
{ value: "format", label: "格式化路径", icon: "format_shapes" },
],
defaultArgvs: {
operation: "normalize",
path: {
value: "",
isString: true,
__varInputVal__: true,
},
paths: [
{
value: "",
isString: true,
__varInputVal__: true,
},
],
from: {
value: "",
isString: true,
__varInputVal__: true,
},
to: {
value: "",
isString: true,
__varInputVal__: true,
},
ext: {
value: "",
isString: true,
__varInputVal__: true,
},
pathObject: {
root: {
value: "",
isString: true,
__varInputVal__: true,
},
dir: {
value: "",
isString: true,
__varInputVal__: true,
},
base: {
value: "",
isString: true,
__varInputVal__: true,
},
name: {
value: "",
isString: true,
__varInputVal__: true,
},
ext: {
value: "",
isString: true,
__varInputVal__: true,
},
},
},
};
},
computed: {
argvs: {
get() {
return (
this.modelValue.argvs ||
this.parseCodeToArgvs(this.modelValue.code) || {
...this.defaultArgvs,
}
);
},
set(value) {
this.updateModelValue(value);
},
},
pointerStyle() {
const activeIndex = this.operations.findIndex(
(op) => op.value === this.argvs.operation
);
if (activeIndex === -1) return {};
const cardWidth = 80;
const gap = 4;
const pointerWidth = 12;
const leftOffset =
(cardWidth + gap) * activeIndex + cardWidth / 2 - pointerWidth / 2;
return {
left: `${leftOffset}px`,
};
},
},
methods: {
generateCode(argvs = this.argvs) {
switch (argvs.operation) {
case "normalize":
case "parse":
case "dirname":
case "extname":
case "isAbsolute":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.path
)})`;
case "basename":
if (argvs.ext && argvs.ext.value) {
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.path
)}, ${stringifyArgv(argvs.ext)})`;
}
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.path
)})`;
case "join":
case "resolve":
return `${this.modelValue.value}.${argvs.operation}(${argvs.paths
.map((p) => stringifyArgv(p))
.join(", ")})`;
case "relative":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.from
)}, ${stringifyArgv(argvs.to)})`;
case "format":
return `${this.modelValue.value}.${argvs.operation}(${stringifyArgv(
argvs.pathObject
)})`;
default:
return `${this.modelValue.value}.${argvs.operation}()`;
}
},
parseCodeToArgvs(code) {
if (!code) return null;
try {
// 使variable
const variableFormatPaths = [
"arg*", //
"arg*.**", //
];
// 使 parseFunction
const result = parseFunction(code, { variableFormatPaths });
if (!result) return this.defaultArgvs;
const operation = result.name.split(".").pop();
const [firstArg, secondArg] = result.argvs;
const newArgvs = {
...this.defaultArgvs,
operation,
};
switch (operation) {
case "normalize":
case "parse":
case "dirname":
case "extname":
case "isAbsolute":
newArgvs.path = firstArg;
break;
case "basename":
newArgvs.path = firstArg;
if (secondArg) {
newArgvs.ext = secondArg;
}
break;
case "join":
case "resolve":
newArgvs.paths = result.argvs.map((arg) => arg);
break;
case "relative":
newArgvs.from = firstArg;
newArgvs.to = secondArg;
break;
case "format":
newArgvs.pathObject = firstArg;
break;
}
return newArgvs;
} catch (e) {
console.error("解析Path参数失败:", e);
return this.defaultArgvs;
}
},
updateArgvs(key, value) {
this.argvs = {
...this.argvs,
[key]: value,
};
},
updatePathAtIndex(index, value) {
const newPaths = [...this.argvs.paths];
newPaths[index] = value;
this.updateArgvs("paths", newPaths);
},
updatePathObject(key, value) {
this.updateArgvs("pathObject", {
...this.argvs.pathObject,
[key]: value,
});
},
addPath() {
this.updateArgvs("paths", [
...this.argvs.paths,
{
value: "",
isString: true,
__varInputVal__: true,
},
]);
},
removePath(index) {
const newPaths = [...this.argvs.paths];
newPaths.splice(index, 1);
this.updateArgvs("paths", newPaths);
},
getSummary(argvs) {
return this.operations.find((op) => op.value === argvs.operation)?.label;
},
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>
.path-editor {
display: flex;
flex-direction: column;
}
.options-container {
min-height: 32px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

View File

@ -60,19 +60,7 @@ export const SystemCommandEditor = defineAsyncComponent(() =>
import("components/composer/system/SystemCommandEditor.vue")
);
export const OsEditor = defineAsyncComponent(() =>
import("components/composer/system/OsEditor.vue")
);
export const PathEditor = defineAsyncComponent(() =>
import("components/composer/system/PathEditor.vue")
);
export const ZlibEditor = defineAsyncComponent(() =>
import("src/components/composer/data/ZlibEditor.vue")
);
export const UrlEditor = defineAsyncComponent(() =>
import("components/composer/network/UrlEditor.vue")
);
export const BufferEditor = defineAsyncComponent(() =>
import("src/components/composer/data/BufferEditor.vue")
);

View File

@ -312,11 +312,355 @@ export const dataCommands = {
saveOutput: true,
},
{
value: "quickcomposer.data.buffer",
value: "quickcomposer.data.buffer.from",
label: "Buffer操作",
desc: "Buffer创建、转换和操作",
component: "BufferEditor",
config: [],
icon: "memory",
functionSelector: [
{
value: "quickcomposer.data.buffer.from",
label: "创建Buffer",
icon: "add_box",
config: [
{
label: "数据",
type: "varInput",
icon: "text_fields",
width: 9,
},
{
label: "编码",
type: "select",
icon: "code",
options: [
{ 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" },
],
defaultValue: "utf8",
width: 3,
},
],
},
{
value: "quickcomposer.data.buffer.toString",
label: "转换字符串",
icon: "text_fields",
config: [
{
label: "Buffer",
type: "varInput",
icon: "memory",
width: 12,
},
{
label: "编码",
type: "select",
icon: "code",
options: [
{ 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" },
],
defaultValue: "utf8",
width: 4,
},
{
label: "起始位置",
type: "numInput",
icon: "first_page",
width: 4,
},
{
label: "结束位置",
type: "numInput",
icon: "last_page",
width: 4,
},
],
},
{
value: "quickcomposer.data.buffer.write",
label: "写入数据",
icon: "edit",
config: [
{
label: "Buffer",
type: "varInput",
icon: "memory",
width: 6,
},
{
label: "要写入的字符串",
type: "varInput",
icon: "edit",
width: 6,
},
{
label: "偏移量",
type: "numInput",
icon: "first_page",
width: 4,
},
{
label: "长度",
type: "numInput",
icon: "straighten",
width: 4,
},
{
label: "编码",
type: "select",
icon: "code",
options: [
{ 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" },
],
defaultValue: "utf8",
width: 4,
},
],
},
{
value: "quickcomposer.data.buffer.fill",
label: "填充数据",
icon: "format_color_fill",
config: [
{
label: "Buffer",
type: "varInput",
icon: "memory",
width: 6,
},
{
label: "填充值",
type: "varInput",
icon: "format_color_fill",
width: 6,
},
{
label: "起始位置",
type: "numInput",
icon: "first_page",
width: 4,
},
{
label: "结束位置",
type: "numInput",
icon: "last_page",
width: 4,
},
{
label: "编码",
type: "select",
icon: "code",
options: [
{ 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" },
],
defaultValue: "utf8",
width: 4,
},
],
},
{
value: "quickcomposer.data.buffer.copy",
label: "复制数据",
icon: "content_copy",
config: [
{
label: "源Buffer",
type: "varInput",
icon: "content_copy",
width: 6,
},
{
label: "目标Buffer",
type: "varInput",
icon: "save",
width: 6,
},
{
label: "目标起始位置",
type: "numInput",
icon: "first_page",
width: 4,
},
{
label: "源起始位置",
type: "numInput",
icon: "first_page",
width: 4,
},
{
label: "源结束位置",
type: "numInput",
icon: "last_page",
width: 4,
},
],
},
{
value: "quickcomposer.data.buffer.compare",
label: "比较数据",
icon: "compare",
config: [
{
label: "Buffer 1",
type: "varInput",
icon: "memory",
width: 6,
},
{
label: "Buffer 2",
type: "varInput",
icon: "memory",
width: 6,
},
],
},
{
value: "quickcomposer.data.buffer.concat",
label: "连接Buffer",
icon: "merge",
config: [
{
label: "Buffer列表",
type: "arrayEditor",
icon: "memory",
width: 12,
defaultValue: [
{
value: "",
isString: false,
__varInputVal__: true,
},
],
},
{
label: "总长度(可选)",
type: "numInput",
icon: "straighten",
width: 12,
},
],
},
{
value: "quickcomposer.data.buffer.slice",
label: "切片数据",
icon: "content_cut",
config: [
{
label: "Buffer",
type: "varInput",
icon: "memory",
width: 12,
},
{
label: "起始位置",
type: "numInput",
icon: "first_page",
width: 6,
},
{
label: "结束位置",
type: "numInput",
icon: "last_page",
width: 6,
},
],
},
{
value: "quickcomposer.data.buffer.indexOf",
label: "查找数据",
icon: "search",
config: [
{
label: "Buffer",
type: "varInput",
icon: "memory",
width: 12,
},
{
label: "要查找的值",
type: "varInput",
icon: "search",
width: 4,
},
{
label: "起始位置",
type: "numInput",
icon: "first_page",
width: 4,
},
{
label: "编码",
type: "select",
icon: "code",
options: [
{ 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" },
],
defaultValue: "utf8",
width: 4,
},
],
},
{
value: "quickcomposer.data.buffer.swap",
label: "交换字节序",
icon: "swap_horiz",
config: [
{
label: "Buffer",
type: "varInput",
icon: "memory",
width: 9,
},
{
label: "字节大小",
type: "select",
icon: "memory",
options: [
{ label: "16位", value: 16 },
{ label: "32位", value: 32 },
{ label: "64位", value: 64 },
],
defaultValue: 16,
width: 3,
},
],
},
],
},
{
value: "quickcomposer.data.zlib",

View File

@ -47,11 +47,181 @@ export const networkCommands = {
saveOutput: true,
},
{
value: "quickcomposer.network.url",
value: "quickcomposer.network.url.parse",
label: "URL操作",
desc: "URL解析、格式化和参数处理",
component: "UrlEditor",
icon: "link",
config: [
{
label: "URL",
type: "varInput",
icon: "link",
width: "auto",
},
],
functionSelector: [
{
value: "quickcomposer.network.url.parse",
label: "解析URL",
icon: "link_off",
},
{
value: "quickcomposer.network.url.format",
label: "格式化URL",
icon: "link",
excludeConfig: [0],
config: [
{
label: "协议",
type: "varInput",
icon: "security",
width: 6,
},
{
label: "认证信息",
type: "varInput",
icon: "person",
width: 6,
},
{
label: "主机名",
type: "varInput",
icon: "dns",
width: 6,
},
{
label: "端口",
type: "varInput",
icon: "settings_ethernet",
width: 6,
},
{
label: "路径",
type: "varInput",
icon: "folder",
},
{
label: "查询字符串",
type: "varInput",
icon: "search",
width: 6,
},
{
label: "锚点",
type: "varInput",
icon: "tag",
width: 6,
},
],
},
{
value: "quickcomposer.network.url.parseQuery",
label: "解析查询字符串",
icon: "search",
excludeConfig: [0],
config: [
{
label: "查询字符串",
type: "varInput",
icon: "search",
},
],
},
{
value: "quickcomposer.network.url.formatQuery",
label: "格式化查询字符串",
icon: "edit",
excludeConfig: [0],
config: [
{
label: "参数",
type: "dictEditor",
icon: "edit",
},
],
},
{
value: "quickcomposer.network.url.parsePath",
label: "解析路径",
icon: "folder_open",
excludeConfig: [0],
config: [
{
label: "路径",
type: "varInput",
icon: "folder",
},
],
},
{
value: "quickcomposer.network.url.parseHost",
label: "解析主机名",
icon: "dns",
excludeConfig: [0],
config: [
{
label: "主机名",
type: "varInput",
icon: "dns",
},
],
},
{
value: "quickcomposer.network.url.getQueryParam",
label: "获取参数",
icon: "find_in_page",
config: [
{
label: "参数名",
type: "varInput",
icon: "key",
width: "auto",
},
],
},
{
value: "quickcomposer.network.url.addQueryParam",
label: "添加参数",
icon: "add_circle",
config: [
{
label: "参数名",
type: "varInput",
icon: "key",
width: "auto",
},
{
label: "参数值",
type: "varInput",
icon: "text_fields",
width: "auto",
},
],
},
{
value: "quickcomposer.network.url.removeQueryParam",
label: "移除参数",
icon: "remove_circle",
config: [
{
label: "参数名",
type: "varInput",
icon: "key",
width: "auto",
},
],
},
{
value: "quickcomposer.network.url.isAbsolute",
label: "检查绝对URL",
icon: "check_circle",
},
{
value: "quickcomposer.network.url.parseComponents",
label: "解析组成部分",
icon: "category",
},
],
},
{
value: "quickcomposer.network.dns.lookupHost",

View File

@ -32,20 +32,252 @@ export const systemCommands = {
isAsync: true,
},
{
value: "quickcomposer.system.os",
value: "quickcomposer.system.os.arch",
label: "系统信息",
desc: "获取操作系统相关信息",
component: "OsEditor",
icon: "computer",
isAsync: true,
config: [],
functionSelector: [
{
value: "quickcomposer.system.os.arch",
label: "系统架构",
icon: "memory",
},
{
value: "quickcomposer.system.os.cpus",
label: "CPU信息",
icon: "developer_board",
config: [
{
label: "信息格式",
type: "buttonGroup",
options: [
{ label: "完整信息", value: "full" },
{ label: "仅型号和速度", value: "simple" },
],
defaultValue: "full",
width: 12,
},
],
},
{
value: "quickcomposer.system.os.memory",
label: "内存信息",
icon: "storage",
config: [
{
label: "内存类型",
type: "buttonGroup",
options: [
{ label: "总内存", value: "totalmem" },
{ label: "空闲内存", value: "freemem" },
],
defaultValue: "totalmem",
width: 12,
},
],
},
{
value: "quickcomposer.system.os.network",
label: "网络信息",
icon: "wifi",
config: [
{
label: "网络信息类型",
type: "buttonGroup",
options: [
{ label: "主机名", value: "hostname" },
{ label: "网络接口", value: "networkInterfaces" },
],
defaultValue: "hostname",
width: 12,
},
{
label: "包含内部接口",
type: "checkbox",
defaultValue: false,
width: 12,
condition: "values[0] === 'networkInterfaces'",
},
],
},
{
value: "quickcomposer.system.os.platform",
label: "平台信息",
icon: "computer",
config: [
{
label: "平台信息类型",
type: "buttonGroup",
options: [
{ label: "操作系统名称", value: "platform" },
{ label: "操作系统类型", value: "type" },
{ label: "操作系统版本", value: "release" },
{ label: "操作系统架构", value: "arch" },
{ label: "CPU字节序", value: "endianness" },
{ label: "系统临时目录", value: "tmpdir" },
{ label: "主目录", value: "homedir" },
{ label: "系统正常运行时间", value: "uptime" },
{ label: "用户信息", value: "userInfo" },
],
defaultValue: "platform",
width: 12,
},
],
},
],
},
{
value: "quickcomposer.system.path",
value: "quickcomposer.system.path.normalize",
label: "路径操作",
desc: "路径操作",
component: "PathEditor",
icon: "folder_path",
isAsync: true,
desc: "路径解析和处理",
icon: "folder",
config: [
{
label: "路径",
type: "varInput",
icon: "folder",
width: "auto",
},
],
functionSelector: [
{
value: "quickcomposer.system.path.normalize",
label: "规范化路径",
icon: "straighten",
},
{
value: "quickcomposer.system.path.parse",
label: "解析路径",
icon: "account_tree",
},
{
value: "quickcomposer.system.path.dirname",
label: "获取目录名",
icon: "folder",
},
{
value: "quickcomposer.system.path.basename",
label: "获取文件名",
icon: "description",
config: [
{
label: "要移除的扩展名",
type: "varInput",
icon: "extension",
width: "auto",
},
],
},
{
value: "quickcomposer.system.path.extname",
label: "获取扩展名",
icon: "extension",
},
{
value: "quickcomposer.system.path.isAbsolute",
label: "判断绝对路径",
icon: "check_circle",
},
{
value: "quickcomposer.system.path.join",
label: "连接路径",
icon: "add_link",
excludeConfig: [0],
config: [
{
label: "路径片段",
type: "varInput",
icon: "folder",
width: "auto",
},
{
label: "路径片段",
type: "varInput",
icon: "folder",
width: "auto",
},
],
},
{
value: "quickcomposer.system.path.resolve",
label: "解析绝对路径",
icon: "assistant_direction",
excludeConfig: [0],
config: [
{
label: "路径片段",
type: "varInput",
icon: "folder",
width: "auto",
},
{
label: "路径片段",
type: "varInput",
icon: "folder",
width: "auto",
},
],
},
{
value: "quickcomposer.system.path.relative",
label: "计算相对路径",
icon: "compare_arrows",
excludeConfig: [0],
config: [
{
label: "起始路径",
type: "varInput",
icon: "folder",
width: 6,
},
{
label: "目标路径",
type: "varInput",
icon: "folder",
width: 6,
},
],
},
{
value: "quickcomposer.system.path.format",
label: "格式化路径",
icon: "format_shapes",
excludeConfig: [0],
config: [
{
label: "根路径",
type: "varInput",
icon: "folder",
width: 6,
},
{
label: "目录",
type: "varInput",
icon: "folder",
width: 6,
},
{
label: "基本名称",
type: "varInput",
icon: "description",
width: 6,
},
{
label: "文件名",
type: "varInput",
icon: "insert_drive_file",
width: 6,
},
{
label: "扩展名",
type: "varInput",
icon: "extension",
width: 6,
},
],
},
],
},
],
};