重构命令输出变量系统:新增OutputEditor组件,支持更灵活的变量配置和回调函数创建

This commit is contained in:
fofolee 2025-01-26 01:36:20 +08:00
parent 0f020d394a
commit 208a6a08d9
21 changed files with 551 additions and 320 deletions

View File

@ -36,7 +36,6 @@
<CommandHead <CommandHead
:command="localCommand" :command="localCommand"
@update:outputVariable="handleOutputVariableUpdate" @update:outputVariable="handleOutputVariableUpdate"
@toggle-output="handleToggleOutput"
@toggle-collapse="handleToggleCollapse" @toggle-collapse="handleToggleCollapse"
@run="runCommand" @run="runCommand"
@remove="$emit('remove')" @remove="$emit('remove')"
@ -96,7 +95,6 @@ import VariableInput from "components/composer/common/VariableInput.vue";
import MultiParams from "components/composer/MultiParams.vue"; import MultiParams from "components/composer/MultiParams.vue";
import CommandHead from "components/composer/card/CommandHead.vue"; import CommandHead from "components/composer/card/CommandHead.vue";
import * as CardComponents from "js/composer/cardComponents"; import * as CardComponents from "js/composer/cardComponents";
import { processVariable } from "js/composer/variableManager";
import { newVarInputVal } from "js/composer/varInputValManager"; import { newVarInputVal } from "js/composer/varInputValManager";
import ControlCommand from "components/composer/control/ControlCommand.vue"; import ControlCommand from "components/composer/control/ControlCommand.vue";
@ -156,24 +154,28 @@ export default defineComponent({
return { getCurrentExistingVar }; return { getCurrentExistingVar };
}, },
methods: { methods: {
handleOutputVariableUpdate(value) { handleOutputVariableUpdate(result) {
const result = processVariable({ const { outputVariable, mode, functionInfo } = result;
value,
existingVars: this.getCurrentExistingVar().map((v) => v.name),
});
if (result.warning) { if (outputVariable.name || outputVariable.details) {
quickcommand.showMessageBox(result.warning, "info"); this.localCommand.outputVariable = { ...outputVariable };
// callbackFunc
if (mode === "callback") {
this.localCommand.callbackFunc = functionInfo.name;
} else {
delete this.localCommand.callbackFunc;
} }
this.localCommand.outputVariable = result.processedValue; //
}, if (mode === "callback" && functionInfo) {
handleToggleOutput() { this.$emit("add-command", {
this.localCommand.saveOutput = !this.localCommand.saveOutput; command: functionInfo,
type: "function",
// });
if (!this.localCommand.saveOutput) { }
this.localCommand.outputVariable = null; } else {
delete this.localCommand.outputVariable;
delete this.localCommand.callbackFunc;
} }
}, },
runCommand() { runCommand() {
@ -182,9 +184,10 @@ export default defineComponent({
// //
const tempCommand = { const tempCommand = {
...this.localCommand, ...this.localCommand,
outputVariable: outputVariable: {
this.localCommand.outputVariable || `temp_${Date.now()}`, name: `temp_${Date.now()}`,
saveOutput: true, ...this.localCommand.outputVariable,
},
}; };
this.$emit("run", tempCommand); this.$emit("run", tempCommand);
}, },
@ -234,12 +237,10 @@ export default defineComponent({
}, },
handleAddPrint() { handleAddPrint() {
// //
if (!this.localCommand.outputVariable) { this.localCommand.outputVariable = {
this.localCommand.outputVariable = `temp_${parseInt( name: `temp_${Date.now()}`,
new Date().getTime() / 1000 ...this.localCommand.outputVariable,
)}`; };
this.localCommand.saveOutput = true;
}
const printCommand = { const printCommand = {
value: "console.log", value: "console.log",
label: "显示消息", label: "显示消息",
@ -250,7 +251,7 @@ export default defineComponent({
icon: "info", icon: "info",
}, },
], ],
argvs: [newVarInputVal("var", this.localCommand.outputVariable)], argvs: [newVarInputVal("var", this.localCommand.outputVariable.name)],
}; };
this.$emit("add-command", { this.$emit("add-command", {
command: printCommand, command: printCommand,

View File

@ -293,12 +293,6 @@ export default defineComponent({
...parsedAction, ...parsedAction,
id: this.getUniqueId(), id: this.getUniqueId(),
}; };
if (newCommand.saveOutput && newCommand.outputVariable) {
newCommand.outputVariable = processVariable({
value: newCommand.outputVariable,
existingVars: this.getCurrentExistingVar().map((v) => v.name),
}).processedValue;
}
return newCommand; return newCommand;
}, },
getUniqueId() { getUniqueId() {
@ -351,7 +345,9 @@ export default defineComponent({
command, command,
{ {
// //
code: `if(${command.outputVariable}!==undefined){console.log(${command.outputVariable})}`, code: `if(${command.outputVariable.name}!==undefined){
console.log(${command.outputVariable.name})
}`,
}, },
], ],
}; };
@ -446,7 +442,13 @@ export default defineComponent({
}); });
return newCommands; return newCommands;
}, },
handleAddCommand({ command, type }, index) { handleAddCommand(event, index) {
const { command, type } = event;
if (type === "function") {
// FlowTabs
this.$emit("action", "addFlow", command);
} else {
//
if (type === "chain") { if (type === "chain") {
// //
const { startIndex, endIndex } = this.getChainIndex(command.chainId); const { startIndex, endIndex } = this.getChainIndex(command.chainId);
@ -465,6 +467,7 @@ export default defineComponent({
newCommands.splice(index + 1, 0, newCommand); newCommands.splice(index + 1, 0, newCommand);
this.$emit("update:modelValue", newCommands); this.$emit("update:modelValue", newCommands);
} }
}
}, },
handleToggleChainDisable({ chainId, disabled }) { handleToggleChainDisable({ chainId, disabled }) {
// //

View File

@ -84,7 +84,7 @@
v-model="showVariableManager" v-model="showVariableManager"
:flow="flow" :flow="flow"
:variables="flow.customVariables" :variables="flow.customVariables"
@update-flow="updateFlow(flow)" @update-flow="Sub(flow)"
:is-main-flow="flow.id === 'main'" :is-main-flow="flow.id === 'main'"
:output-variables="outputVariables" :output-variables="outputVariables"
class="variable-panel" class="variable-panel"
@ -102,7 +102,6 @@ import FlowManager from "components/composer/flow/FlowManager.vue";
import { generateCode } from "js/composer/generateCode"; import { generateCode } from "js/composer/generateCode";
import { findCommandByValue } from "js/composer/composerConfig"; import { findCommandByValue } from "js/composer/composerConfig";
import { generateUniqSuffix } from "js/composer/variableManager"; import { generateUniqSuffix } from "js/composer/variableManager";
import { parseVariables } from "js/composer/variableManager";
export default defineComponent({ export default defineComponent({
name: "FlowTabs", name: "FlowTabs",
components: { components: {
@ -152,9 +151,10 @@ export default defineComponent({
const getOutputVariables = (flow = getCurrentFlow()) => { const getOutputVariables = (flow = getCurrentFlow()) => {
const variables = []; const variables = [];
for (const [index, cmd] of flow.commands.entries()) { for (const [index, cmd] of flow.commands.entries()) {
if (cmd.saveOutput && cmd.outputVariable) { if (cmd.outputVariable) {
const { name, details = {} } = cmd.outputVariable;
variables.push( variables.push(
...parseVariables(cmd.outputVariable).map((variable) => ({ ...[name, ...Object.values(details)].map((variable) => ({
name: variable, name: variable,
// //
sourceCommand: { sourceCommand: {
@ -231,16 +231,43 @@ export default defineComponent({
) )
); );
}, },
addFlow() { addFlow(options = {}) {
const id = this.$root.getUniqueId(); const id = this.$root.getUniqueId();
const name = this.generateFlowName(); const name = options.name || this.generateFlowName();
this.subFlows.push({ const newFlow = {
id, id,
name, name,
label: name.replace("func_", "函数"), label: name.replace("func_", "函数"),
commands: [], commands: [],
customVariables: [], customVariables: [],
};
//
if (options.params) {
options.params.forEach((param) => {
newFlow.customVariables.push({
name: param,
type: "param",
}); });
});
}
//
if (options.localVars && options.localVars.length > 0) {
options.localVars.forEach((varInfo) => {
newFlow.customVariables.push({
name: varInfo.name,
type: "var",
value: varInfo.value,
});
});
}
this.subFlows.push(newFlow);
if (options.params || options.localVars) {
return;
}
this.activeTab = id; this.activeTab = id;
this.$nextTick(() => { this.$nextTick(() => {
this.toggleVariableManager(); this.toggleVariableManager();
@ -253,6 +280,16 @@ export default defineComponent({
this.activeTab = this.flows[0].id; this.activeTab = this.flows[0].id;
} }
}, },
updateSubFlow(index, payload) {
const { params, localVars } = payload;
this.subFlows[index].customVariables = [
...params.map((param) => ({
name: param,
type: "param",
})),
...localVars,
];
},
generateFlowCode(flow) { generateFlowCode(flow) {
return generateCode(flow); return generateCode(flow);
}, },
@ -282,6 +319,16 @@ export default defineComponent({
case "toggleVariableManager": case "toggleVariableManager":
this.toggleVariableManager(); this.toggleVariableManager();
break; break;
case "addFlow":
//
const index = this.subFlows.findIndex((f) => f.name === payload.name);
if (index > -1) {
//
this.updateSubFlow(index, payload);
} else {
this.addFlow(payload);
}
break;
default: default:
this.$emit("action", type, this.generateAllFlowCode()); this.$emit("action", type, this.generateAllFlowCode());
} }
@ -295,18 +342,22 @@ export default defineComponent({
...flow, ...flow,
commands: flow.commands.map((cmd) => { commands: flow.commands.map((cmd) => {
const cmdCopy = { ...cmd }; const cmdCopy = { ...cmd };
// //
const uselessProps = [ const uselessProps = [
"config", "config",
"code", "code",
"label", "label",
"component", "component",
"subCommands", "subCommands",
"outputs",
"options", "options",
"defaultValue", "defaultValue",
"icon", "icon",
"width", "width",
"placeholder", "placeholder",
"isAsync",
"summary",
"type",
]; ];
uselessProps.forEach((prop) => delete cmdCopy[prop]); uselessProps.forEach((prop) => delete cmdCopy[prop]);
return cmdCopy; return cmdCopy;
@ -323,6 +374,7 @@ export default defineComponent({
const newFlows = flowsData.map((flow) => ({ const newFlows = flowsData.map((flow) => ({
...flow, ...flow,
commands: flow.commands.map((cmd) => { commands: flow.commands.map((cmd) => {
//
const command = findCommandByValue(cmd.value); const command = findCommandByValue(cmd.value);
return { return {
...command, ...command,
@ -330,7 +382,7 @@ export default defineComponent({
}; };
}), }),
})); }));
this.updateFlow(newFlows); this.Sub(newFlows);
this.activeTab = this.mainFlow.id; this.activeTab = this.mainFlow.id;
}, },
runFlows(flow) { runFlows(flow) {
@ -358,7 +410,7 @@ export default defineComponent({
this.activeTab = flow.id; this.activeTab = flow.id;
this.toggleVariableManager(); this.toggleVariableManager();
}, },
updateFlow(flow) { Sub(flow) {
this.mainFlow = flow[0]; this.mainFlow = flow[0];
this.subFlows = flow.slice(1); this.subFlows = flow.slice(1);
}, },

View File

@ -6,39 +6,18 @@
class="output-section row items-center no-wrap" class="output-section row items-center no-wrap"
v-if="!isControlFlow" v-if="!isControlFlow"
> >
<!-- 变量输入框 --> <!-- 输出变量按钮 -->
<q-input
v-if="command.saveOutput"
v-model="inputValue"
@focus="sourceValue = inputValue"
@blur="handleBlur"
outlined
placeholder="变量名"
class="variable-input"
align="center"
>
</q-input>
<!-- 保存变量按钮 -->
<q-icon <q-icon
:name="command.saveOutput ? 'data_object' : 'output'" name="output"
v-if="!command.neverHasOutput" v-if="!command.neverHasOutput"
class="output-btn" class="output-btn"
@click="$emit('toggle-output')" :color="command.outputVariable ? 'primary' : ''"
@click="showOutputEditor = true"
> >
<q-tooltip> <q-tooltip>
<div class="text-body2"> <div class="text-body2">配置命令输出变量</div>
{{
command.saveOutput
? "当前命令的输出将保存到变量中"
: "点击将此命令的输出保存为变量以供后续使用"
}}
</div>
<div class="text-caption text-grey-5"> <div class="text-caption text-grey-5">
{{ 将命令的输出保存为变量以供后续使用
command.saveOutput
? "点击取消输出到变量"
: "保存后可在其他命令中使用此变量"
}}
</div> </div>
</q-tooltip> </q-tooltip>
</q-icon> </q-icon>
@ -113,12 +92,24 @@
</q-icon> </q-icon>
</div> </div>
</div> </div>
<!-- 输出编辑器 -->
<OutputEditor
v-model="showOutputEditor"
:command="command"
@confirm="$emit('update:outputVariable', $event)"
/>
</div> </div>
</template> </template>
<script> <script>
import OutputEditor from "./OutputEditor.vue";
export default { export default {
name: "CommandButtons", name: "CommandButtons",
components: {
OutputEditor,
},
props: { props: {
command: { command: {
type: Object, type: Object,
@ -143,18 +134,11 @@ export default {
}, },
data() { data() {
return { return {
inputValue: this.command.outputVariable || "", showOutputEditor: false,
sourceValue: "",
}; };
}, },
watch: {
"command.outputVariable"(newVal) {
this.inputValue = newVal || "";
},
},
emits: [ emits: [
"update:outputVariable", "update:outputVariable",
"toggle-output",
"run", "run",
"remove", "remove",
"toggle-collapse", "toggle-collapse",
@ -162,17 +146,6 @@ export default {
"toggle-disable", "toggle-disable",
"add-print", "add-print",
], ],
methods: {
handleBlur() {
//
if (
this.inputValue.replace(/[ ]/g, "") ===
this.sourceValue.replace(/[ ]/g, "")
)
return;
this.$emit("update:outputVariable", this.inputValue);
},
},
}; };
</script> </script>
@ -185,37 +158,9 @@ export default {
/* 输出部分样式 */ /* 输出部分样式 */
.output-section { .output-section {
/* margin-right: 8px; */
gap: 8px; gap: 8px;
} }
.variable-input {
width: 120px;
}
.output-section :deep(.q-field) {
border-radius: 4px;
}
.output-section :deep(.q-field__control) {
height: 20px;
min-height: 20px;
padding: 0 4px;
}
.output-section :deep(.q-field__marginal) {
height: 20px;
width: 24px;
min-width: 24px;
}
.output-section :deep(.q-field__native) {
padding: 0;
font-size: 12px;
min-height: 20px;
text-align: center;
}
/* 按钮样式 */ /* 按钮样式 */
.output-btn, .output-btn,
.run-btn, .run-btn,
@ -251,14 +196,6 @@ export default {
} }
/* 暗色模式适配 */ /* 暗色模式适配 */
.body--dark .output-section :deep(.q-field) {
background: rgba(255, 255, 255, 0.03);
}
.body--dark .output-section :deep(.q-field--focused) {
background: #1d1d1d;
}
.body--dark .output-btn { .body--dark .output-btn {
border-color: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.1);
} }

View File

@ -46,7 +46,6 @@
:isFirstCommandInChain="isFirstCommandInChain" :isFirstCommandInChain="isFirstCommandInChain"
:isLastCommandInChain="isLastCommandInChain" :isLastCommandInChain="isLastCommandInChain"
@update:outputVariable="$emit('update:outputVariable', $event)" @update:outputVariable="$emit('update:outputVariable', $event)"
@toggle-output="$emit('toggle-output')"
@run="$emit('run')" @run="$emit('run')"
@remove="$emit('remove')" @remove="$emit('remove')"
/> />
@ -67,13 +66,7 @@ export default {
required: true, required: true,
}, },
}, },
emits: [ emits: ["update:outputVariable", "run", "remove", "toggle-collapse"],
"update:outputVariable",
"toggle-output",
"run",
"remove",
"toggle-collapse",
],
computed: { computed: {
contentClass() { contentClass() {
return { return {

View File

@ -0,0 +1,318 @@
<template>
<q-dialog v-model="isOpen" persistent>
<q-card class="output-editor q-px-sm">
<div class="row justify-center q-px-sm q-pt-md">
{{ commandName }}
</div>
<div class="simple-output q-px-sm">
<q-badge color="primary" class="q-mb-sm q-pa-xs">完整结果</q-badge>
<q-input v-model="simpleOutputVar" filled dense autofocus>
<template v-slot:prepend>
<div class="variable-label">
{{ currentOutputs?.label || "输出变量名" }}
</div>
</template>
</q-input>
</div>
<div v-if="hasNestedFields(currentOutputs)">
<q-badge color="primary" class="q-ma-sm q-pa-xs">详细输出</q-badge>
<q-scroll-area
style="height: 200px"
:thumb-style="{
width: '2px',
}"
>
<div class="detail-output column q-col-gutter-sm q-px-sm">
<div v-for="(output, key) in detailOutputs" :key="key">
<!-- 如果是嵌套对象 -->
<div v-if="hasNestedFields(output)">
<BorderLabel :label="output.label || key" :model-value="false">
<div class="column q-col-gutter-sm">
<div
v-for="(subOutput, subKey) in getNestedFields(output)"
:key="subKey"
>
<div class="output-item">
<q-input
v-model="outputVars[`${key}.${subKey}`]"
filled
dense
autofocus
class="col"
:placeholder="subOutput.placeholder"
>
<template v-slot:prepend>
<div class="variable-label">
{{ subOutput.label }}
</div>
</template>
</q-input>
</div>
</div>
</div>
</BorderLabel>
</div>
<!-- 如果是普通字段 -->
<div v-else class="output-item">
<q-input
v-model="outputVars[key]"
filled
dense
class="col"
:placeholder="output.placeholder"
autofocus
>
<template v-slot:prepend>
<div class="variable-label">{{ output.label }}</div>
</template>
</q-input>
</div>
</div>
</div>
</q-scroll-area>
</div>
<div v-if="isAsyncCommand">
<q-badge color="primary" class="q-ma-sm q-pa-xs">输出模式</q-badge>
<div class="row q-col-gutter-sm q-px-sm">
<q-select
v-model="outputMode"
:options="outputModeOptions"
filled
dense
autofocus
emit-value
map-options
class="col"
>
</q-select>
<q-input
v-model="callbackFunc"
filled
dense
autofocus
class="col-8"
v-if="outputMode === 'callback'"
>
<template v-slot:prepend>
<div class="variable-label">回调函数名</div>
</template>
</q-input>
</div>
</div>
<div class="row justify-end q-px-sm q-py-sm">
<q-btn flat label="取消" color="primary" v-close-popup />
<q-btn flat label="确定" color="primary" @click="handleConfirm" />
</div>
</q-card>
</q-dialog>
</template>
<script>
import { defineComponent } from "vue";
import BorderLabel from "components/composer/common/BorderLabel.vue";
export default defineComponent({
name: "OutputEditor",
components: {
BorderLabel,
},
props: {
modelValue: {
type: Boolean,
default: false,
},
command: {
type: Object,
required: true,
},
},
emits: ["update:modelValue", "confirm"],
computed: {
isOpen: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
currentSubCommand() {
if (!this.command.subCommands) return {};
return this.command.subCommands.find(
(cmd) => cmd.value === this.command.value
);
},
commandName() {
return this.currentSubCommand.label || this.command.label;
},
isAsyncCommand() {
return this.currentSubCommand.isAsync || this.command.isAsync;
},
currentOutputs() {
return this.currentSubCommand.outputs || this.command.outputs;
},
detailOutputs() {
let outputs = { ...this.currentOutputs };
delete outputs.label;
delete outputs.placeholder;
return outputs;
},
},
data() {
return {
simpleOutputVar: "",
outputVars: {},
outputMode: "wait",
outputModeOptions: [
{
label: "等待运行完毕",
value: "wait",
},
{
label: "输出到回调函数",
value: "callback",
},
],
callbackFunc: "",
};
},
watch: {
"command.outputVariable": {
immediate: true,
deep: true,
handler(outputVariable) {
this.initOutputVars(outputVariable);
},
},
"command.callbackFunc": {
immediate: true,
handler(callbackFunc) {
if (callbackFunc) {
this.outputMode = "callback";
this.callbackFunc = callbackFunc;
} else {
this.outputMode = "wait";
}
},
},
},
methods: {
hasNestedFields(output) {
console.log(output);
return Object.keys(output).some(
(key) => key !== "label" && key !== "placeholder"
);
},
getNestedFields(output) {
const fields = {};
Object.entries(output).forEach(([key, value]) => {
if (key !== "label" && key !== "placeholder") {
fields[key] = value;
}
});
return fields;
},
initOutputVars(outputVariable) {
//
if (!outputVariable) return;
this.simpleOutputVar = outputVariable.name || "";
if (this.currentOutputs) {
// 使
this.outputVars = outputVariable?.details || {};
}
},
handleConfirm() {
const outputVariable = {
name: this.simpleOutputVar,
};
if (this.currentOutputs) {
const flatVars = {};
Object.entries(this.outputVars).forEach(([path, value]) => {
if (!value) return; //
flatVars[path] = value;
});
//
if (Object.keys(flatVars).length > 0) {
outputVariable.details = flatVars;
}
}
//
const result = {
outputVariable,
mode: this.outputMode,
};
//
if (this.outputMode === "callback" && this.callbackFunc) {
//
result.functionInfo = {
name: this.callbackFunc,
params: [outputVariable.name],
localVars: outputVariable.details
? Object.entries(outputVariable.details).map(([path, varName]) => ({
name: varName,
type: "var",
value: `${outputVariable.name}.${path}`,
}))
: [],
};
}
this.$emit("confirm", result);
this.isOpen = false;
},
},
});
</script>
<style scoped>
.output-editor {
width: 450px;
}
.output-item {
border-radius: 8px;
transition: all 0.3s ease;
}
.output-item:hover {
background: rgba(0, 0, 0, 0.02);
}
.variable-label {
font-size: 12px;
border-radius: 4px;
padding-right: 10px;
text-align: center;
}
.body--dark .output-item:hover {
background: rgba(255, 255, 255, 0.02);
}
.output-editor :deep(.q-field--filled .q-field__control),
.output-editor :deep(.q-field--filled .q-field__control > *),
.output-editor :deep(.q-field--filled .q-field__native) {
max-height: 36px;
min-height: 36px;
border-radius: 5px;
font-size: 12px;
}
/* 去除filled输入框边框 */
.output-editor :deep(.q-field__control:before) {
border: none;
}
/* 去除filled输入框下划线 */
.output-editor :deep(.q-field__control:after) {
height: 0;
border-bottom: none;
}
</style>

View File

@ -9,6 +9,9 @@
<div <div
v-for="opt in options" v-for="opt in options"
:key="opt.value" :key="opt.value"
:style="{
height: height,
}"
:class="['button-item', { active: modelValue === opt.value }]" :class="['button-item', { active: modelValue === opt.value }]"
@click="$emit('update:modelValue', opt.value)" @click="$emit('update:modelValue', opt.value)"
> >
@ -31,6 +34,10 @@ export default defineComponent({
modelValue: { modelValue: {
required: true, required: true,
}, },
height: {
type: String,
default: "26px",
},
options: { options: {
type: Array, type: Array,
required: true, required: true,
@ -67,7 +74,6 @@ export default defineComponent({
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 26px;
padding: 0 12px; padding: 0 12px;
font-size: 12px; font-size: 12px;
border-radius: 4px; border-radius: 4px;

View File

@ -60,28 +60,33 @@
<q-tooltip>载入</q-tooltip> <q-tooltip>载入</q-tooltip>
</q-btn> </q-btn>
<q-separator vertical /> <q-separator vertical />
<q-btn <q-btn flat dense icon="preview" @click="isVisible = true">
flat <q-tooltip>预览代码</q-tooltip>
dense
icon="preview"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
</q-btn> </q-btn>
<q-btn dense flat icon="play_circle" @click="$emit('action', 'run')"> <q-btn dense flat icon="play_circle" @click="$emit('action', 'run')">
<q-tooltip>运行</q-tooltip> <q-tooltip>运行</q-tooltip>
</q-btn> </q-btn>
</div> </div>
<transition name="preview-fade"> <q-dialog v-model="isVisible">
<div v-if="isVisible" class="preview-popup"> <q-card style="width: 550px">
<div class="preview-header"> <q-card-section class="row items-center q-py-xs q-px-md">
<q-icon name="code" size="16px" class="q-mr-xs" /> <div>
<span>预览代码</span> <q-icon name="code" size="16px" class="q-mr-sm" />
预览代码
</div> </div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator />
<q-scroll-area style="height: 400px">
<pre class="preview-code"><code>{{ code }}</code></pre> <pre class="preview-code"><code>{{ code }}</code></pre>
</div> </q-scroll-area>
</transition> </q-card-section>
</q-card>
</q-dialog>
</div> </div>
</template> </template>
@ -112,27 +117,16 @@ export default defineComponent({
return { return {
isVisible: false, isVisible: false,
code: "", code: "",
previewTimer: null,
isDev: window.utools.isDev(), isDev: window.utools.isDev(),
}; };
}, },
methods: { watch: {
handleMouseEnter() { isVisible(val) {
this.previewTimer = setTimeout(() => { if (val) {
this.code = this.generateCode(); this.code = this.generateCode();
this.isVisible = true; }
}, 200);
}, },
handleMouseLeave() {
clearTimeout(this.previewTimer);
this.isVisible = false;
},
},
beforeUnmount() {
clearTimeout(this.previewTimer);
}, },
}); });
</script> </script>
@ -165,31 +159,6 @@ export default defineComponent({
color: var(--q-primary); color: var(--q-primary);
} }
.preview-popup {
position: absolute;
top: 40px;
right: 30px;
min-width: 300px;
max-width: 600px;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
z-index: 1000;
transform-origin: center right;
}
.preview-header {
padding: 10px 14px;
background: rgba(var(--q-primary-rgb), 0.03);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
border-radius: 8px 8px 0 0;
font-size: 13px;
font-weight: 500;
color: var(--q-primary);
display: flex;
align-items: center;
}
.preview-code { .preview-code {
margin: 0; margin: 0;
padding: 14px; padding: 14px;
@ -215,30 +184,4 @@ export default defineComponent({
.preview-code::-webkit-scrollbar-thumb:hover { .preview-code::-webkit-scrollbar-thumb:hover {
background: var(--q-primary-opacity-30); background: var(--q-primary-opacity-30);
} }
/* 过渡动画 */
.preview-fade-enter-active,
.preview-fade-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.preview-fade-enter-from,
.preview-fade-leave-to {
opacity: 0;
transform: translateX(20px) scale(0.95);
}
/* 暗色模式适配 */
.body--dark .preview-popup {
background: #1d1d1d;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.body--dark .preview-header {
background: rgba(255, 255, 255, 0.03);
}
.body--dark .preview-code {
color: #e0e0e0;
}
</style> </style>

View File

@ -7,8 +7,6 @@ export const codingCommands = {
value: "quickcomposer.coding.base64Encode", value: "quickcomposer.coding.base64Encode",
label: "编解码", label: "编解码",
icon: "code", icon: "code",
outputVariable: "processedText",
saveOutput: true,
config: [ config: [
{ {
label: "要编解码的文本", label: "要编解码的文本",
@ -63,22 +61,16 @@ export const codingCommands = {
value: "quickcomposer.coding.symmetricCrypto", value: "quickcomposer.coding.symmetricCrypto",
label: "对称加解密", label: "对称加解密",
component: "SymmetricCryptoEditor", component: "SymmetricCryptoEditor",
outputVariable: "processedText",
saveOutput: true,
}, },
{ {
value: "quickcomposer.coding.asymmetricCrypto", value: "quickcomposer.coding.asymmetricCrypto",
label: "非对称加解密", label: "非对称加解密",
component: "AsymmetricCryptoEditor", component: "AsymmetricCryptoEditor",
outputVariable: "processedText",
saveOutput: true,
}, },
{ {
value: "quickcomposer.coding.md5Hash", value: "quickcomposer.coding.md5Hash",
label: "哈希计算", label: "哈希计算",
icon: "enhanced_encryption", icon: "enhanced_encryption",
outputVariable: "hashValue",
saveOutput: true,
config: [ config: [
{ {
label: "要计算哈希的文本", label: "要计算哈希的文本",

View File

@ -355,8 +355,6 @@ export const dataCommands = {
componentProps: { componentProps: {
inputLabel: "要处理的文本", inputLabel: "要处理的文本",
}, },
outputVariable: "processedText",
saveOutput: true,
}, },
{ {
value: "quickcomposer.data.buffer.from", value: "quickcomposer.data.buffer.from",

View File

@ -13,6 +13,33 @@ export const macosCommands = {
value: "quickcomposer.macos.app.getFrontmost", value: "quickcomposer.macos.app.getFrontmost",
label: "获取前台应用", label: "获取前台应用",
icon: "front_hand", icon: "front_hand",
outputs: {
label: "前台应用信息",
name: { label: "应用名称" },
displayedName: { label: "应用显示名称" },
path: { label: "应用路径" },
version: { label: "应用版本" },
pid: { label: "应用进程ID" },
backgroundOnly: { label: "是否后台运行" },
visible: { label: "是否可见" },
frontmost: { label: "是否前台运行" },
window: {
label: "窗口信息",
name: { label: "窗口名称" },
title: { label: "窗口标题" },
index: { label: "窗口索引" },
position: {
label: "窗口位置",
placeholder: "数组, 第一个元素是 x 坐标,第二个元素是 y 坐标",
},
size: {
label: "窗口大小",
placeholder: "数组, 第一个元素是宽度,第二个元素是高度",
},
minimized: { label: "是否最小化" },
fullscreen: { label: "是否全屏" },
},
},
}, },
{ {
value: "quickcomposer.macos.app.getRunningApps", value: "quickcomposer.macos.app.getRunningApps",

View File

@ -43,8 +43,6 @@ export const networkCommands = {
component: "AxiosConfigEditor", component: "AxiosConfigEditor",
isAsync: true, isAsync: true,
icon: "http", icon: "http",
outputVariable: "{data}",
saveOutput: true,
}, },
{ {
value: "quickcomposer.network.url.parse", value: "quickcomposer.network.url.parse",

View File

@ -73,8 +73,6 @@ export const screenCommands = {
value: "utools.getPrimaryDisplay", value: "utools.getPrimaryDisplay",
label: "获取显示器信息", label: "获取显示器信息",
icon: "monitor", icon: "monitor",
outputVariable: "display",
saveOutput: true,
subCommands: [ subCommands: [
{ {
value: "utools.getPrimaryDisplay", value: "utools.getPrimaryDisplay",
@ -104,8 +102,6 @@ export const screenCommands = {
value: "utools.screenToDipPoint", value: "utools.screenToDipPoint",
label: "物理/DIP坐标转换", label: "物理/DIP坐标转换",
icon: "transform", icon: "transform",
outputVariable: "{x,y}",
saveOutput: true,
config: [XY_DICT_EDITOR], config: [XY_DICT_EDITOR],
subCommands: [ subCommands: [
{ {
@ -124,8 +120,6 @@ export const screenCommands = {
value: "utools.screenToDipRect", value: "utools.screenToDipRect",
label: "物理/DIP区域转换", label: "物理/DIP区域转换",
icon: "transform", icon: "transform",
outputVariable: "{x,y,width,height}",
saveOutput: true,
config: [RECT_DICT_EDITOR], config: [RECT_DICT_EDITOR],
subCommands: [ subCommands: [
{ {

View File

@ -223,16 +223,12 @@ export const simulateCommands = {
label: "屏幕取色", label: "屏幕取色",
icon: "colorize", icon: "colorize",
isAsync: true, isAsync: true,
outputVariable: "{hex,rgb}",
saveOutput: true,
}, },
{ {
value: "quickcomposer.simulate.captureScreen", value: "quickcomposer.simulate.captureScreen",
label: "屏幕截图", label: "屏幕截图",
icon: "screenshot_monitor", icon: "screenshot_monitor",
isAsync: true, isAsync: true,
outputVariable: "base64Data",
saveOutput: true,
config: [ config: [
{ {
label: "截图范围", label: "截图范围",

View File

@ -7,40 +7,30 @@ export const statusCommands = {
label: "获取当前文件管理器路径", label: "获取当前文件管理器路径",
icon: "folder", icon: "folder",
isAsync: true, isAsync: true,
outputVariable: "currentFolderPath",
saveOutput: true,
}, },
{ {
value: "utools.readCurrentBrowserUrl", value: "utools.readCurrentBrowserUrl",
label: "获取当前浏览器地址", label: "获取当前浏览器地址",
icon: "language", icon: "language",
isAsync: true, isAsync: true,
outputVariable: "currentBrowserUrl",
saveOutput: true,
}, },
{ {
value: "quickcomposer.status.getSelectedText", value: "quickcomposer.status.getSelectedText",
label: "获取选中文本", label: "获取选中文本",
icon: "text_fields", icon: "text_fields",
isAsync: true, isAsync: true,
outputVariable: "selectedText",
saveOutput: true,
}, },
{ {
value: "quickcomposer.status.getSelectedImage", value: "quickcomposer.status.getSelectedImage",
label: "获取选中的图片", label: "获取选中的图片",
icon: "image", icon: "image",
isAsync: true, isAsync: true,
outputVariable: "selectedImage",
saveOutput: true,
}, },
{ {
value: "quickcomposer.status.getSelectedFiles", value: "quickcomposer.status.getSelectedFiles",
label: "获取选中的文件", label: "获取选中的文件",
icon: "file_present", icon: "file_present",
isAsync: true, isAsync: true,
outputVariable: "selectedFiles",
saveOutput: true,
}, },
], ],
}; };

View File

@ -72,8 +72,6 @@ export const systemCommands = {
{ {
value: "electron.clipboard.readText", value: "electron.clipboard.readText",
label: "读取剪贴板", label: "读取剪贴板",
outputVariable: "clipboardContent",
saveOutput: true,
subCommands: [ subCommands: [
{ {
value: "electron.clipboard.readText", value: "electron.clipboard.readText",
@ -112,8 +110,6 @@ export const systemCommands = {
{ {
value: "utools.getPath", value: "utools.getPath",
label: "获取系统路径", label: "获取系统路径",
outputVariable: "systemPath",
saveOutput: true,
defaultValue: "home", defaultValue: "home",
config: [ config: [
{ {

View File

@ -169,8 +169,9 @@ export const uiCommands = {
value: "quickcommand.showConfirmBox", value: "quickcommand.showConfirmBox",
label: "确认框", label: "确认框",
isAsync: true, isAsync: true,
outputVariable: "confirmed", outputs: {
saveOutput: true, label: "是否确认",
},
config: [ config: [
{ {
label: "提示内容", label: "提示内容",
@ -219,8 +220,6 @@ export const uiCommands = {
value: "quickcommand.showButtonBox", value: "quickcommand.showButtonBox",
label: "按钮组", label: "按钮组",
isAsync: true, isAsync: true,
outputVariable: "{id,text}",
saveOutput: true,
width: 12, width: 12,
config: [ config: [
{ {
@ -256,8 +255,6 @@ export const uiCommands = {
value: "quickcommand.showInputBox", value: "quickcommand.showInputBox",
label: "输入框", label: "输入框",
isAsync: true, isAsync: true,
outputVariable: "[inputValue1]",
saveOutput: true,
config: [ config: [
{ {
label: "输入框", label: "输入框",
@ -302,8 +299,6 @@ export const uiCommands = {
value: "quickcommand.showTextArea", value: "quickcommand.showTextArea",
label: "文本框", label: "文本框",
isAsync: true, isAsync: true,
outputVariable: "textareaValue",
saveOutput: true,
config: [ config: [
{ {
label: "文本框占位符", label: "文本框占位符",
@ -372,8 +367,6 @@ export const uiCommands = {
{ {
value: "utools.showOpenDialog", value: "utools.showOpenDialog",
label: "文件选择框", label: "文件选择框",
outputVariable: "filePaths",
saveOutput: true,
subCommands: [ subCommands: [
{ {
value: "utools.showOpenDialog", value: "utools.showOpenDialog",

View File

@ -14,15 +14,11 @@ export const userdataCommands = {
icon: "title", icon: "title",
}, },
], ],
outputVariable: "userData",
saveOutput: true,
}, },
{ {
value: "quickcommand.userData.all", value: "quickcommand.userData.all",
label: "获取所有用户数据", label: "获取所有用户数据",
icon: "database", icon: "database",
outputVariable: "userDatas",
saveOutput: true,
}, },
{ {
value: "quickcommand.userData.put", value: "quickcommand.userData.put",

View File

@ -47,15 +47,11 @@ export const utoolsCommands = {
value: "utools.isDarkColors", value: "utools.isDarkColors",
label: "是否深色模式", label: "是否深色模式",
icon: "dark_mode", icon: "dark_mode",
outputVariable: "isDark",
saveOutput: true,
}, },
{ {
value: "utools.getUser", value: "utools.getUser",
label: "获取用户信息", label: "获取用户信息",
icon: "person", icon: "person",
outputVariable: "{avatar,nickname,type}",
saveOutput: true,
}, },
{ {
value: "utools.redirect", value: "utools.redirect",
@ -163,20 +159,14 @@ export const utoolsCommands = {
value: "utools.getWindowType", value: "utools.getWindowType",
label: "获取当前窗口类型", label: "获取当前窗口类型",
icon: "window", icon: "window",
outputVariable: "windowType",
saveOutput: true,
}, },
{ {
value: "utools.getNativeId", value: "utools.getNativeId",
label: "获取本地ID", label: "获取本地ID",
outputVariable: "nativeId",
saveOutput: true,
}, },
{ {
value: "utools.getAppVersion", value: "utools.getAppVersion",
label: "获取uTools版本", label: "获取uTools版本",
outputVariable: "appVersion",
saveOutput: true,
}, },
], ],
}; };

View File

@ -207,8 +207,6 @@ export const windowsCommands = {
value: "quickcomposer.windows.window.getWindowInfo", value: "quickcomposer.windows.window.getWindowInfo",
label: "搜索窗口", label: "搜索窗口",
icon: "search", icon: "search",
outputVariable: "windowInfo",
saveOutput: true,
}, },
{ {
value: "quickcomposer.windows.automation.inspect", value: "quickcomposer.windows.automation.inspect",
@ -451,7 +449,6 @@ export const windowsCommands = {
value: "quickcomposer.windows.automation.getvalue", value: "quickcomposer.windows.automation.getvalue",
label: "获取值", label: "获取值",
icon: "content_paste", icon: "content_paste",
outputVariable: "elementValue",
}, },
{ {
value: "quickcomposer.windows.automation.select", value: "quickcomposer.windows.automation.select",
@ -621,8 +618,6 @@ export const windowsCommands = {
value: "quickcomposer.windows.sendmessage.listControls", value: "quickcomposer.windows.sendmessage.listControls",
label: "获取控件树", label: "获取控件树",
icon: "account_tree", icon: "account_tree",
outputVariable: "controlsTree",
saveOutput: true,
config: [ config: [
{ {
component: "OptionEditor", component: "OptionEditor",
@ -810,8 +805,6 @@ export const windowsCommands = {
label: "剪贴板/文件监控", label: "剪贴板/文件监控",
icon: "monitor_heart", icon: "monitor_heart",
isAsync: true, isAsync: true,
outputVariable: "monitorEvent",
saveOutput: true,
showLoading: true, showLoading: true,
subCommands: [ subCommands: [
{ {
@ -879,8 +872,6 @@ export const windowsCommands = {
value: "quickcomposer.windows.process.listProcesses", value: "quickcomposer.windows.process.listProcesses",
label: "进程列表", label: "进程列表",
icon: "list", icon: "list",
outputVariable: "processList",
saveOutput: true,
}, },
{ {
value: "quickcomposer.windows.process.killProcess", value: "quickcomposer.windows.process.killProcess",
@ -956,8 +947,6 @@ export const windowsCommands = {
value: "quickcomposer.windows.registry.listKeys", value: "quickcomposer.windows.registry.listKeys",
label: "列出项", label: "列出项",
icon: "list", icon: "list",
outputVariable: "registryKeys",
saveOutput: true,
}, },
{ {
value: "quickcomposer.windows.registry.getValue", value: "quickcomposer.windows.registry.getValue",
@ -983,8 +972,6 @@ export const windowsCommands = {
placeholder: "要获取的值名称", placeholder: "要获取的值名称",
}, },
], ],
outputVariable: "registryValue",
saveOutput: true,
}, },
{ {
value: "quickcomposer.windows.registry.setValue", value: "quickcomposer.windows.registry.setValue",
@ -1072,8 +1059,6 @@ export const windowsCommands = {
value: "quickcomposer.windows.service.listServices", value: "quickcomposer.windows.service.listServices",
label: "服务列表", label: "服务列表",
icon: "list", icon: "list",
outputVariable: "serviceList",
saveOutput: true,
}, },
{ {
value: "quickcomposer.windows.service.controlService", value: "quickcomposer.windows.service.controlService",
@ -1116,8 +1101,6 @@ export const windowsCommands = {
value: "quickcomposer.windows.software.listSoftware", value: "quickcomposer.windows.software.listSoftware",
label: "软件列表", label: "软件列表",
icon: "list", icon: "list",
outputVariable: "softwareList",
saveOutput: true,
}, },
{ {
value: "quickcomposer.windows.software.uninstallSoftware", value: "quickcomposer.windows.software.uninstallSoftware",

View File

@ -2,8 +2,7 @@ export function generateCode(flow) {
const { commands, name, label, customVariables = [] } = flow; const { commands, name, label, customVariables = [] } = flow;
const params = customVariables.filter((v) => v.type === "param") || []; const params = customVariables.filter((v) => v.type === "param") || [];
const manualVars = const manualVars = customVariables.filter((v) => v.type === "var") || [];
customVariables.filter((v) => v.type === "var") || [];
// 检查是否包含异步函数 // 检查是否包含异步函数
const hasAsyncFunction = commands.some((cmd) => cmd.isAsync); const hasAsyncFunction = commands.some((cmd) => cmd.isAsync);
@ -25,15 +24,41 @@ export function generateCode(flow) {
// 跳过禁用的命令 // 跳过禁用的命令
if (cmd.disabled) return; if (cmd.disabled) return;
if (!cmd.code) return; if (!cmd.code) return;
let line = indent;
let cmdCode = cmd.code;
// 处理输出变量
if (cmd.outputVariable) { if (cmd.outputVariable) {
line += `let ${cmd.outputVariable} = `; const { name, details } = cmd.outputVariable;
if (cmd.isAsync) {
if (cmd.callbackFunc) {
// 使用回调函数模式
cmdCode = `${cmdCode}.then(${cmd.callbackFunc})`;
} else {
// 使用 await 模式
cmdCode = `const ${name} = await ${cmdCode}`;
code.push(indent + cmdCode);
// 处理详细变量
if (details) {
Object.entries(details).forEach(([path, varName]) => {
code.push(`${indent}let ${varName} = ${name}.${path};`);
});
}
return;
}
} else {
cmdCode = `const ${name} = ${cmdCode}`;
code.push(indent + cmdCode);
// 处理详细变量
if (details) {
Object.entries(details).forEach(([path, varName]) => {
code.push(`${indent}let ${varName} = ${name}.${path};`);
});
}
return;
}
} }
let awaitCmd = cmd.isAsync ? "await " : ""; code.push(indent + cmdCode);
line += `${awaitCmd} ${cmd.code}`;
code.push(line);
}); });
code.push("}"); // Close the function code.push("}"); // Close the function