mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-08 14:34:13 +08:00
完善条件判断的删除添加逻辑,添加编排卡片折叠功能
This commit is contained in:
parent
f3a01a1ba9
commit
37ebebc5ac
@ -1,12 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="composer-card" :class="{ 'can-drop': canDrop }" v-bind="$attrs">
|
<div
|
||||||
|
class="composer-card"
|
||||||
|
:class="{
|
||||||
|
collapsed: isCollapsed && !command.isControlFlow,
|
||||||
|
'drag-handle': !isLastCommandInChain,
|
||||||
|
'no-animation': isClickingControl,
|
||||||
|
}"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@mousedown="handleMouseDown"
|
||||||
|
@mouseup="handleMouseUp"
|
||||||
|
>
|
||||||
<q-card class="command-item">
|
<q-card class="command-item">
|
||||||
<q-card-section class="q-pa-sm">
|
<q-card-section
|
||||||
|
class="card-section"
|
||||||
|
:class="{ collapsed: isCollapsed || command.isControlFlow }"
|
||||||
|
>
|
||||||
<CommandHead
|
<CommandHead
|
||||||
:command="command"
|
:command="command"
|
||||||
:is-control-flow="command.isControlFlow"
|
:is-collapsed="isCollapsed"
|
||||||
@update:outputVariable="handleOutputVariableUpdate"
|
@update:outputVariable="handleOutputVariableUpdate"
|
||||||
@toggle-output="handleToggleOutput"
|
@toggle-output="handleToggleOutput"
|
||||||
|
@toggle-collapse="handleToggleCollapse"
|
||||||
@run="runCommand"
|
@run="runCommand"
|
||||||
@remove="$emit('remove')"
|
@remove="$emit('remove')"
|
||||||
>
|
>
|
||||||
@ -29,21 +43,27 @@
|
|||||||
</CommandHead>
|
</CommandHead>
|
||||||
|
|
||||||
<!-- 非控制流程组件的参数输入 -->
|
<!-- 非控制流程组件的参数输入 -->
|
||||||
<div v-if="!command.isControlFlow" class="row items-center q-mt-sm">
|
<div
|
||||||
<component
|
v-if="!command.isControlFlow"
|
||||||
v-if="!!command.component"
|
class="command-content-wrapper"
|
||||||
:is="command.component"
|
:class="{ collapsed: isCollapsed }"
|
||||||
v-model="argvLocal"
|
>
|
||||||
:command="command"
|
<div class="command-content">
|
||||||
class="col"
|
<component
|
||||||
v-bind="command.componentProps || {}"
|
v-if="!!command.component"
|
||||||
/>
|
:is="command.component"
|
||||||
<MultiParamInput
|
v-model="argvLocal"
|
||||||
v-else
|
:command="command"
|
||||||
v-model="argvLocal"
|
class="col"
|
||||||
:command="command"
|
v-bind="command.componentProps || {}"
|
||||||
class="col"
|
/>
|
||||||
/>
|
<MultiParamInput
|
||||||
|
v-else
|
||||||
|
v-model="argvLocal"
|
||||||
|
:command="command"
|
||||||
|
class="col"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
@ -101,7 +121,7 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
canDrop: {
|
isDragging: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
@ -109,8 +129,20 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showKeyRecorder: false,
|
showKeyRecorder: false,
|
||||||
|
isCollapsed: false,
|
||||||
|
isClickingControl: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
"command.isCollapsed": {
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
if (val !== undefined) {
|
||||||
|
this.isCollapsed = val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
emits: [
|
emits: [
|
||||||
"remove",
|
"remove",
|
||||||
"toggle-output",
|
"toggle-output",
|
||||||
@ -118,6 +150,7 @@ export default defineComponent({
|
|||||||
"update:command",
|
"update:command",
|
||||||
"run",
|
"run",
|
||||||
"addBranch",
|
"addBranch",
|
||||||
|
"toggle-collapse",
|
||||||
],
|
],
|
||||||
computed: {
|
computed: {
|
||||||
saveOutputLocal: {
|
saveOutputLocal: {
|
||||||
@ -166,6 +199,12 @@ export default defineComponent({
|
|||||||
showOutputBtn() {
|
showOutputBtn() {
|
||||||
return !this.command.isControlFlow;
|
return !this.command.isControlFlow;
|
||||||
},
|
},
|
||||||
|
isLastCommandInChain() {
|
||||||
|
if (!this.command.commandChain) return false;
|
||||||
|
return (
|
||||||
|
this.command.commandType === this.command.commandChain?.slice(-1)[0]
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const addVariable = inject("addVariable");
|
const addVariable = inject("addVariable");
|
||||||
@ -259,6 +298,31 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
this.$emit("run", tempCommand);
|
this.$emit("run", tempCommand);
|
||||||
},
|
},
|
||||||
|
handleToggleCollapse() {
|
||||||
|
if (this.command.isControlFlow) {
|
||||||
|
// 控制命令的折叠,直接传递给父组件处理
|
||||||
|
this.$emit("toggle-collapse", {
|
||||||
|
isCollapsed: this.isCollapsed,
|
||||||
|
chainId: this.command.chainId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 非控制命令的折叠,更新自身状态
|
||||||
|
this.isCollapsed = !this.isCollapsed;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMouseDown(event) {
|
||||||
|
// 检查点击的元素是否是控制元素(按钮、输入框等)
|
||||||
|
const isControlElement = event.target.closest(
|
||||||
|
".q-btn, .q-field, .q-icon, button, input, .border-label"
|
||||||
|
);
|
||||||
|
this.isClickingControl = !!isControlElement;
|
||||||
|
},
|
||||||
|
handleMouseUp() {
|
||||||
|
// 延迟重置状态,以确保动画不会立即触发
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isClickingControl = false;
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -269,28 +333,68 @@ export default defineComponent({
|
|||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
padding: 2px 2px 2px 0;
|
|
||||||
transform: translateY(0) scale(1);
|
transform: translateY(0) scale(1);
|
||||||
|
border-radius: inherit;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card.no-animation,
|
||||||
|
.composer-card.no-animation::before,
|
||||||
|
.composer-card.no-animation .command-item {
|
||||||
|
transition: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card.drag-handle {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card.no-animation.drag-handle {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card.drag-handle:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card.drag-handle:hover::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--q-primary);
|
||||||
|
opacity: 0.03;
|
||||||
|
border-radius: inherit;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card.drag-handle:active::before {
|
||||||
|
opacity: 0.06;
|
||||||
|
transform: scale(0.99);
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-item {
|
.command-item {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: inherit;
|
||||||
|
user-select: none;
|
||||||
|
transform: translateZ(0);
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-item:hover {
|
.drag-handle .command-item {
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
transition: transform 0.2s ease, box-shadow 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 拖拽和放置样式 */
|
.drag-handle:hover .command-item {
|
||||||
.can-drop {
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
transform: scale(1.02);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.can-drop .command-item {
|
.drag-handle:active .command-item {
|
||||||
border: 2px dashed var(--q-primary);
|
transform: translateY(0) scale(0.98);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 暗色模式适配 */
|
/* 暗色模式适配 */
|
||||||
@ -298,12 +402,61 @@ export default defineComponent({
|
|||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.body--dark .command-item:hover {
|
.body--dark .drag-handle:hover .command-item {
|
||||||
box-shadow: 0 4px 8px rgba(58, 58, 58, 0.3);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.body--dark .can-drop {
|
.body--dark .drag-handle:active .command-item {
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .composer-card.drag-handle:hover::before {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .composer-card.drag-handle:active::before {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 收起状态样式 */
|
||||||
|
.composer-card.collapsed {
|
||||||
|
transform-origin: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card.collapsed .command-item {
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片内容区域动画 */
|
||||||
|
.card-section {
|
||||||
|
transition: padding 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-section.collapsed {
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 命令内容动画 */
|
||||||
|
.command-content-wrapper {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
transition: grid-template-rows 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-content {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-content-wrapper.collapsed {
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-content-wrapper.collapsed .command-content {
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 调整控制流程组件的样式 */
|
/* 调整控制流程组件的样式 */
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="composer-flow">
|
<div class="composer-flow">
|
||||||
<ChainStyles ref="chainStyles" :commands="commands" />
|
<ChainStyles ref="chainStyles" :commands="commands" />
|
||||||
<div class="section-header">
|
<div class="section-header flow-header">
|
||||||
<q-icon name="timeline" size="20px" class="q-mx-sm text-primary" />
|
<div class="flow-title">
|
||||||
<span class="text-subtitle1">命令流程</span>
|
<q-icon name="timeline" size="20px" class="q-mx-sm text-primary" />
|
||||||
<q-space />
|
<span class="text-subtitle1">命令流程</span>
|
||||||
|
</div>
|
||||||
<ComposerButtons
|
<ComposerButtons
|
||||||
:generate-code="generateCode"
|
:generate-code="generateCode"
|
||||||
@action="$emit('action', $event)"
|
:is-all-collapsed="isAllCollapsed"
|
||||||
|
@action="handleAction"
|
||||||
|
class="flex-grow"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -23,8 +26,8 @@
|
|||||||
group="commands"
|
group="commands"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
class="flow-list"
|
class="flow-list"
|
||||||
handle=".drag-handle"
|
|
||||||
:animation="200"
|
:animation="200"
|
||||||
|
handle=".drag-handle"
|
||||||
@start="onDragStart"
|
@start="onDragStart"
|
||||||
@end="onDragEnd"
|
@end="onDragEnd"
|
||||||
@change="onDragChange"
|
@change="onDragChange"
|
||||||
@ -39,6 +42,7 @@
|
|||||||
'insert-after':
|
'insert-after':
|
||||||
dragIndex === commands.length &&
|
dragIndex === commands.length &&
|
||||||
index === commands.length - 1,
|
index === commands.length - 1,
|
||||||
|
...getCollapsedChainClass(index),
|
||||||
...getChainGroupClass(index),
|
...getChainGroupClass(index),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
@ -51,6 +55,7 @@
|
|||||||
@update:command="(val) => updateCommand(index, val)"
|
@update:command="(val) => updateCommand(index, val)"
|
||||||
@run="handleRunCommand"
|
@run="handleRunCommand"
|
||||||
@add-branch="(chainInfo) => addBranch(index, chainInfo)"
|
@add-branch="(chainInfo) => addBranch(index, chainInfo)"
|
||||||
|
@toggle-collapse="(event) => handleControlFlowCollapse(event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
@ -67,7 +72,7 @@
|
|||||||
import { defineComponent, inject } from "vue";
|
import { defineComponent, inject } from "vue";
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import ComposerCard from "./ComposerCard.vue";
|
import ComposerCard from "./ComposerCard.vue";
|
||||||
import ComposerButtons from "./ComposerButtons.vue";
|
import ComposerButtons from "./flow/ComposerButtons.vue";
|
||||||
import ChainStyles from "./flow/ChainStyles.vue";
|
import ChainStyles from "./flow/ChainStyles.vue";
|
||||||
import EmptyFlow from "./flow/EmptyFlow.vue";
|
import EmptyFlow from "./flow/EmptyFlow.vue";
|
||||||
import DropArea from "./flow/DropArea.vue";
|
import DropArea from "./flow/DropArea.vue";
|
||||||
@ -102,6 +107,8 @@ export default defineComponent({
|
|||||||
dragIndex: -1,
|
dragIndex: -1,
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
draggedCommand: null,
|
draggedCommand: null,
|
||||||
|
collapsedRanges: [],
|
||||||
|
isAllCollapsed: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -284,15 +291,53 @@ export default defineComponent({
|
|||||||
getUniqueId() {
|
getUniqueId() {
|
||||||
return this.$root.getUniqueId();
|
return this.$root.getUniqueId();
|
||||||
},
|
},
|
||||||
|
isFirstCommandInChain(command) {
|
||||||
|
if (!command.commandChain) return false;
|
||||||
|
return command.commandType === command.commandChain?.[0];
|
||||||
|
},
|
||||||
|
removeRangeCommand(startIndex, endIndex, chainId) {
|
||||||
|
if (!endIndex) endIndex = startIndex;
|
||||||
|
const newCommands = [...this.commands];
|
||||||
|
// 从后往前删除,避免索引变化
|
||||||
|
for (let i = endIndex; i >= startIndex; i--) {
|
||||||
|
const cmd = newCommands[i];
|
||||||
|
// 如果chainId不为空,则只删除指定chainId的命令
|
||||||
|
if (chainId && cmd.chainId !== chainId) continue;
|
||||||
|
if (cmd.outputVariable) {
|
||||||
|
this.removeVariable(cmd.outputVariable);
|
||||||
|
}
|
||||||
|
newCommands.splice(i, 1);
|
||||||
|
}
|
||||||
|
this.$emit("update:modelValue", newCommands);
|
||||||
|
},
|
||||||
removeCommand(index) {
|
removeCommand(index) {
|
||||||
const command = this.commands[index];
|
const command = this.commands[index];
|
||||||
// 如果命令有输出变量,需要先清理
|
|
||||||
if (command.outputVariable) {
|
// 如果是控制流程的起始命令
|
||||||
this.removeVariable(command.outputVariable);
|
if (this.isFirstCommandInChain(command)) {
|
||||||
|
// 显示确认对话框
|
||||||
|
quickcommand
|
||||||
|
.showButtonBox(["全部删除", "保留内部命令", "手抖👋🏻"])
|
||||||
|
.then(({ id }) => {
|
||||||
|
if (id !== 0 && id !== 1) return;
|
||||||
|
const newCommands = [...this.commands];
|
||||||
|
const chainId = command.chainId;
|
||||||
|
const lastIndex = newCommands.findLastIndex(
|
||||||
|
(cmd) => cmd.chainId === chainId
|
||||||
|
);
|
||||||
|
const startIndex = newCommands.findIndex(
|
||||||
|
(cmd) => cmd.chainId === chainId
|
||||||
|
);
|
||||||
|
this.removeRangeCommand(
|
||||||
|
startIndex,
|
||||||
|
lastIndex,
|
||||||
|
id === 0 ? null : chainId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 如果不是控制流程的起始命令,直接删除
|
||||||
|
this.removeRangeCommand(index);
|
||||||
}
|
}
|
||||||
const newCommands = [...this.commands];
|
|
||||||
newCommands.splice(index, 1);
|
|
||||||
this.$emit("update:modelValue", newCommands);
|
|
||||||
},
|
},
|
||||||
toggleSaveOutput(index) {
|
toggleSaveOutput(index) {
|
||||||
const newCommands = [...this.commands];
|
const newCommands = [...this.commands];
|
||||||
@ -358,6 +403,87 @@ export default defineComponent({
|
|||||||
this.$emit("update:modelValue", newCommands);
|
this.$emit("update:modelValue", newCommands);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleControlFlowCollapse(event) {
|
||||||
|
const chainId = event.chainId;
|
||||||
|
const isCollapsed = !event.isCollapsed; // 取反,因为我们要切换状态
|
||||||
|
if (!chainId) return;
|
||||||
|
|
||||||
|
// 遍历commands找到相同chainId的第一个和最后一个命令的index
|
||||||
|
const startIndex = this.commands.findIndex(
|
||||||
|
(cmd) => cmd.chainId === chainId
|
||||||
|
);
|
||||||
|
const endIndex = this.commands.findLastIndex(
|
||||||
|
(cmd) => cmd.chainId === chainId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (startIndex === -1 || endIndex === -1) return;
|
||||||
|
|
||||||
|
// 更新命令的折叠状态
|
||||||
|
const newCommands = [...this.commands];
|
||||||
|
newCommands[startIndex] = {
|
||||||
|
...newCommands[startIndex],
|
||||||
|
isCollapsed,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$emit("update:modelValue", newCommands);
|
||||||
|
|
||||||
|
if (isCollapsed) {
|
||||||
|
// 折叠命令:添加新的折叠区间
|
||||||
|
this.collapsedRanges.push({
|
||||||
|
chainId,
|
||||||
|
start: startIndex,
|
||||||
|
end: endIndex,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 展开命令:移除对应的折叠区间
|
||||||
|
const existingRangeIndex = this.collapsedRanges.findIndex(
|
||||||
|
(range) => range.chainId === chainId
|
||||||
|
);
|
||||||
|
if (existingRangeIndex !== -1) {
|
||||||
|
this.collapsedRanges.splice(existingRangeIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getCollapsedChainClass(index) {
|
||||||
|
// 找出所有包含当前index的折叠区间
|
||||||
|
const matchingRanges = this.collapsedRanges.filter(
|
||||||
|
(range) => index >= range.start && index <= range.end
|
||||||
|
);
|
||||||
|
if (!matchingRanges.length) return {};
|
||||||
|
// 检查是否是任意区间的中间或结束位置
|
||||||
|
const isAnyMiddleEnd = matchingRanges.some(
|
||||||
|
(range) => index > range.start && index <= range.end
|
||||||
|
);
|
||||||
|
// 只要在任何区间内部,无论是否是开始位置,都返回hidden样式,解决嵌套问题
|
||||||
|
return isAnyMiddleEnd
|
||||||
|
? { "collapsed-chain-hidden": true }
|
||||||
|
: { "collapsed-chain-start": true };
|
||||||
|
},
|
||||||
|
handleAction(action, payload) {
|
||||||
|
if (action === "collapseAll") {
|
||||||
|
this.collapseAll();
|
||||||
|
} else if (action === "expandAll") {
|
||||||
|
this.expandAll();
|
||||||
|
} else {
|
||||||
|
this.$emit("action", action, payload);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
collapseAll() {
|
||||||
|
const newCommands = this.commands.map((cmd) => ({
|
||||||
|
...cmd,
|
||||||
|
isCollapsed: true,
|
||||||
|
}));
|
||||||
|
this.$emit("update:modelValue", newCommands);
|
||||||
|
this.isAllCollapsed = true;
|
||||||
|
},
|
||||||
|
expandAll() {
|
||||||
|
const newCommands = this.commands.map((cmd) => ({
|
||||||
|
...cmd,
|
||||||
|
isCollapsed: false,
|
||||||
|
}));
|
||||||
|
this.$emit("update:modelValue", newCommands);
|
||||||
|
this.isAllCollapsed = false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -377,6 +503,17 @@ export default defineComponent({
|
|||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-grow {
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-scroll {
|
.command-scroll {
|
||||||
@ -461,10 +598,49 @@ export default defineComponent({
|
|||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.03), 0 0 4px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.03), 0 0 4px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 拖拽时的卡片效果 */
|
/* 流程卡片 */
|
||||||
.flow-item {
|
.flow-item {
|
||||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin: 3px 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏的链式命令 */
|
||||||
|
.collapsed-chain-hidden {
|
||||||
|
grid-template-rows: 0fr !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsed-chain-hidden > * {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item.chain-start {
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item.chain-start.collapsed-chain-start {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item.chain-middle {
|
||||||
|
border-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item.chain-end {
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-item.insert-before {
|
.flow-item.insert-before {
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
<!-- 输出变量设置和按钮 -->
|
<!-- 输出变量设置和按钮 -->
|
||||||
<div
|
<div
|
||||||
class="output-section row items-center no-wrap"
|
class="output-section row items-center no-wrap"
|
||||||
v-if="!showDeleteOnly"
|
v-if="!isControlFlow"
|
||||||
>
|
>
|
||||||
<!-- 变量输入框 -->
|
<!-- 变量输入框 -->
|
||||||
<q-input
|
<q-input
|
||||||
v-if="command.saveOutput"
|
v-if="command.saveOutput"
|
||||||
:model-value="command.outputVariable"
|
:model-value="command.outputVariable"
|
||||||
@update:model-value="$emit('update:outputVariable', $event)"
|
@update:model-value="$emit('update:outputVariable', $event)"
|
||||||
dense
|
|
||||||
outlined
|
outlined
|
||||||
placeholder="变量名"
|
placeholder="变量名"
|
||||||
class="variable-input"
|
class="variable-input"
|
||||||
@ -19,13 +18,9 @@
|
|||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
<!-- 保存变量按钮 -->
|
<!-- 保存变量按钮 -->
|
||||||
<q-btn
|
<q-icon
|
||||||
:icon="command.saveOutput ? 'data_object' : 'output'"
|
:name="command.saveOutput ? 'data_object' : 'output'"
|
||||||
:label="command.saveOutput ? '保存到变量' : '获取输出'"
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
class="output-btn"
|
class="output-btn"
|
||||||
size="sm"
|
|
||||||
@click="$emit('toggle-output')"
|
@click="$emit('toggle-output')"
|
||||||
>
|
>
|
||||||
<q-tooltip>
|
<q-tooltip>
|
||||||
@ -44,35 +39,28 @@
|
|||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮组 -->
|
<!-- 操作按钮组 -->
|
||||||
<div class="action-buttons row items-center no-wrap">
|
<div class="action-buttons row items-center no-wrap">
|
||||||
<q-btn
|
<q-icon
|
||||||
flat
|
v-if="!isControlFlow"
|
||||||
dense
|
name="play_arrow"
|
||||||
v-if="!showDeleteOnly"
|
class="run-btn"
|
||||||
round
|
|
||||||
icon="play_arrow"
|
|
||||||
class="run-btn q-mr-xs"
|
|
||||||
size="sm"
|
|
||||||
@click="$emit('run')"
|
@click="$emit('run')"
|
||||||
>
|
>
|
||||||
<q-tooltip>单独运行此命令并打印输出</q-tooltip>
|
<q-tooltip>单独运行此命令并打印输出</q-tooltip>
|
||||||
</q-btn>
|
</q-icon>
|
||||||
|
|
||||||
<q-btn
|
<q-icon
|
||||||
flat
|
name="close"
|
||||||
round
|
|
||||||
dense
|
|
||||||
icon="close"
|
|
||||||
@click="$emit('remove')"
|
@click="$emit('remove')"
|
||||||
size="sm"
|
|
||||||
class="remove-btn"
|
class="remove-btn"
|
||||||
|
v-if="!isLastCommandInChain"
|
||||||
>
|
>
|
||||||
<q-tooltip>移除此命令</q-tooltip>
|
<q-tooltip>移除此命令</q-tooltip>
|
||||||
</q-btn>
|
</q-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -86,12 +74,30 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
showDeleteOnly: {
|
isCollapsed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isControlFlow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isFirstCommandInChain: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isLastCommandInChain: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ["update:outputVariable", "toggle-output", "run", "remove"],
|
emits: [
|
||||||
|
"update:outputVariable",
|
||||||
|
"toggle-output",
|
||||||
|
"run",
|
||||||
|
"remove",
|
||||||
|
"toggle-collapse",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -99,11 +105,12 @@ export default {
|
|||||||
.command-buttons {
|
.command-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 输出部分样式 */
|
/* 输出部分样式 */
|
||||||
.output-section {
|
.output-section {
|
||||||
margin-right: 8px;
|
/* margin-right: 8px; */
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,13 +123,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.output-section :deep(.q-field__control) {
|
.output-section :deep(.q-field__control) {
|
||||||
height: 28px;
|
height: 20px;
|
||||||
min-height: 28px;
|
min-height: 20px;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.output-section :deep(.q-field__marginal) {
|
.output-section :deep(.q-field__marginal) {
|
||||||
height: 28px;
|
height: 20px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
}
|
}
|
||||||
@ -130,7 +137,7 @@ export default {
|
|||||||
.output-section :deep(.q-field__native) {
|
.output-section :deep(.q-field__native) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
min-height: 28px;
|
min-height: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,18 +145,20 @@ export default {
|
|||||||
.output-btn,
|
.output-btn,
|
||||||
.run-btn,
|
.run-btn,
|
||||||
.remove-btn {
|
.remove-btn {
|
||||||
font-size: 12px;
|
font-size: 18px;
|
||||||
border-radius: 4px;
|
min-height: 25px;
|
||||||
min-height: 28px;
|
cursor: pointer;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.output-btn:hover,
|
.output-btn:hover,
|
||||||
.run-btn:hover,
|
.run-btn:hover,
|
||||||
.remove-btn:hover {
|
.remove-btn:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1.05);
|
transform: scale(1.1) translateY(-1px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.run-btn:hover {
|
.run-btn:hover {
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="row items-center">
|
||||||
class="row items-center"
|
<!-- 折叠按钮 -->
|
||||||
:class="{
|
<div class="collapse-btn" v-if="!isLastCommandInChain">
|
||||||
'q-pb-sm': !isControlFlow,
|
<q-btn
|
||||||
}"
|
:icon="isCollapsed ? 'expand_more' : 'expand_less'"
|
||||||
>
|
dense
|
||||||
<!-- 拖拽手柄 -->
|
flat
|
||||||
<div class="drag-handle q-mr-sm" draggable="true">
|
size="sm"
|
||||||
<q-icon name="drag_indicator" size="18px" class="text-grey-6" />
|
@click="$emit('toggle-collapse')"
|
||||||
|
>
|
||||||
|
<q-tooltip>折叠/展开此{{ isControlFlow ? "流程" : "命令" }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div v-else class="end-icon">
|
||||||
|
<q-icon name="last_page" size="xs" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
@ -23,8 +29,11 @@
|
|||||||
<!-- 按钮组 -->
|
<!-- 按钮组 -->
|
||||||
<CommandButtons
|
<CommandButtons
|
||||||
:command="command"
|
:command="command"
|
||||||
:show-delete-only="isControlFlow"
|
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
|
:isCollapsed="isCollapsed"
|
||||||
|
:isControlFlow="isControlFlow"
|
||||||
|
:isFirstCommandInChain="isFirstCommandInChain"
|
||||||
|
:isLastCommandInChain="isLastCommandInChain"
|
||||||
@update:outputVariable="$emit('update:outputVariable', $event)"
|
@update:outputVariable="$emit('update:outputVariable', $event)"
|
||||||
@toggle-output="$emit('toggle-output')"
|
@toggle-output="$emit('toggle-output')"
|
||||||
@run="$emit('run')"
|
@run="$emit('run')"
|
||||||
@ -46,50 +55,66 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
isControlFlow: {
|
isCollapsed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ["update:outputVariable", "toggle-output", "run", "remove"],
|
emits: [
|
||||||
|
"update:outputVariable",
|
||||||
|
"toggle-output",
|
||||||
|
"run",
|
||||||
|
"remove",
|
||||||
|
"toggle-collapse",
|
||||||
|
],
|
||||||
computed: {
|
computed: {
|
||||||
contentClass() {
|
contentClass() {
|
||||||
return {
|
return {
|
||||||
col: true,
|
col: true,
|
||||||
"q-ml-md": !this.isControlFlow,
|
"q-ml-md": !this.command.isControlFlow,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
isControlFlow() {
|
||||||
|
return this.command.isControlFlow;
|
||||||
|
},
|
||||||
|
isFirstCommandInChain() {
|
||||||
|
if (!this.command.commandChain) return false;
|
||||||
|
return this.command.commandType === this.command.commandChain?.[0];
|
||||||
|
},
|
||||||
|
isLastCommandInChain() {
|
||||||
|
if (!this.command.commandChain) return false;
|
||||||
|
return (
|
||||||
|
this.command.commandType === this.command.commandChain?.slice(-1)[0]
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.drag-handle {
|
.collapse-btn,
|
||||||
|
.end-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 4px;
|
padding-right: 4px;
|
||||||
cursor: grab;
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-btn :deep(.q-btn),
|
||||||
|
.end-icon {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
transition: all 0.2s ease;
|
min-height: 20px;
|
||||||
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row:hover .drag-handle {
|
.collapse-btn :deep(.q-btn:hover) {
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle:hover {
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
transform: scale(1.1) translateY(-1px);
|
||||||
color: var(--q-primary);
|
color: var(--q-primary);
|
||||||
transform: scale(1.2);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-label {
|
.command-label {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="conditional-judgment">
|
<div class="conditional-judgment">
|
||||||
<div class="row items-center no-wrap">
|
<div class="row items-end no-wrap">
|
||||||
<!-- 类型标签 -->
|
<!-- 类型标签 -->
|
||||||
<div class="text-subtitle2 type-label">
|
<div class="text-subtitle2 type-label">
|
||||||
<template v-if="type === 'if'">如果满足</template>
|
<template v-if="type === 'if'">如果满足</template>
|
||||||
@ -14,7 +14,6 @@
|
|||||||
<q-btn
|
<q-btn
|
||||||
v-if="type === 'if'"
|
v-if="type === 'if'"
|
||||||
flat
|
flat
|
||||||
round
|
|
||||||
dense
|
dense
|
||||||
size="sm"
|
size="sm"
|
||||||
icon="add"
|
icon="add"
|
||||||
@ -33,10 +32,9 @@
|
|||||||
<q-btn
|
<q-btn
|
||||||
v-if="type === 'else'"
|
v-if="type === 'else'"
|
||||||
flat
|
flat
|
||||||
round
|
|
||||||
dense
|
dense
|
||||||
size="sm"
|
size="sm"
|
||||||
:icon="showCondition ? 'unfold_less' : 'unfold_more'"
|
:icon="showCondition ? 'remove' : 'add'"
|
||||||
class="control-btn q-mx-xs"
|
class="control-btn q-mx-xs"
|
||||||
@click="toggleCondition"
|
@click="toggleCondition"
|
||||||
>
|
>
|
||||||
@ -49,7 +47,6 @@
|
|||||||
v-model="conditionLocal"
|
v-model="conditionLocal"
|
||||||
dense
|
dense
|
||||||
borderless
|
borderless
|
||||||
:bg-color="$q.dark.isActive ? 'grey-9' : 'grey-2'"
|
|
||||||
placeholder="输入条件表达式"
|
placeholder="输入条件表达式"
|
||||||
class="condition-input"
|
class="condition-input"
|
||||||
/>
|
/>
|
||||||
@ -161,10 +158,6 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.conditional-judgment {
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-label {
|
.type-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--q-primary);
|
color: var(--q-primary);
|
||||||
@ -178,17 +171,19 @@ export default defineComponent({
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.condition-input :deep(.q-field__control) {
|
.condition-input :deep(.q-field__control),
|
||||||
padding: 0 16px;
|
.condition-input :deep(.q-field__native) {
|
||||||
height: 24px !important;
|
padding: 1px;
|
||||||
min-height: 24px;
|
height: 21px !important;
|
||||||
|
min-height: 21px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn {
|
.control-btn {
|
||||||
width: 24px;
|
width: 21px;
|
||||||
height: 24px;
|
height: 21px;
|
||||||
min-height: 24px;
|
min-height: 21px;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
@ -198,11 +193,6 @@ export default defineComponent({
|
|||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 暗色模式适配 */
|
|
||||||
.body--dark .condition-input {
|
|
||||||
background: rgba(255, 255, 255, 0.05) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body--dark .type-label {
|
.body--dark .type-label {
|
||||||
color: var(--q-primary);
|
color: var(--q-primary);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
@ -1,9 +1,83 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="'style'" v-if="chainStyles">{{ chainStyles }}</component>
|
<component :is="'style'" v-if="styles">{{ styles }}</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, ref, watchEffect, computed } from "vue";
|
||||||
|
|
||||||
|
// 样式常量
|
||||||
|
const STYLE_CONSTANTS = {
|
||||||
|
goldenRatio: 0.618033988749895,
|
||||||
|
hueStep: 360 * 0.618033988749895,
|
||||||
|
indent: 5,
|
||||||
|
lightSl: "60%, 60%",
|
||||||
|
darkSl: "60%, 40%",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
const generateStyleString = (selector, rules) => {
|
||||||
|
return `${selector} {
|
||||||
|
${Object.entries(rules)
|
||||||
|
.map(
|
||||||
|
([prop, value]) =>
|
||||||
|
` ${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${value} !important;`
|
||||||
|
)
|
||||||
|
.join("\n")}
|
||||||
|
}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateShadows = (
|
||||||
|
parentChainIds,
|
||||||
|
hue,
|
||||||
|
indent,
|
||||||
|
lightSl,
|
||||||
|
darkSl,
|
||||||
|
uniqueChainIds
|
||||||
|
) => {
|
||||||
|
return parentChainIds.reduce(
|
||||||
|
(acc, parentChainId, i) => {
|
||||||
|
const parentIndex = uniqueChainIds.indexOf(parentChainId);
|
||||||
|
const parentHue = (parentIndex * STYLE_CONSTANTS.hueStep) % 360;
|
||||||
|
const start = -((i + 2) * indent);
|
||||||
|
acc.light.push(`${start}px 0 0 0 hsl(${parentHue}, ${lightSl})`);
|
||||||
|
acc.dark.push(`${start}px 0 0 0 hsl(${parentHue}, ${darkSl})`);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
light: [`-${indent}px 0 0 0 hsl(${hue}, ${lightSl})`],
|
||||||
|
dark: [`-${indent}px 0 0 0 hsl(${hue}, ${darkSl})`],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取链组
|
||||||
|
const getChainGroups = (commands) => {
|
||||||
|
const groups = [];
|
||||||
|
const activeGroups = new Map();
|
||||||
|
|
||||||
|
for (let i = 0; i < commands.length; i++) {
|
||||||
|
const cmd = commands[i];
|
||||||
|
if (!cmd.chainId) continue;
|
||||||
|
|
||||||
|
if (cmd.commandType === cmd.commandChain?.[0]) {
|
||||||
|
activeGroups.set(cmd.chainId, {
|
||||||
|
chainId: cmd.chainId,
|
||||||
|
startIndex: i,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
cmd.commandType === cmd.commandChain?.[cmd.commandChain.length - 1]
|
||||||
|
) {
|
||||||
|
const group = activeGroups.get(cmd.chainId);
|
||||||
|
if (group) {
|
||||||
|
group.endIndex = i;
|
||||||
|
groups.push({ ...group });
|
||||||
|
activeGroups.delete(cmd.chainId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "ChainStyles",
|
name: "ChainStyles",
|
||||||
@ -13,204 +87,119 @@ export default defineComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
setup(props) {
|
||||||
uniqueChainIds() {
|
const styles = ref("");
|
||||||
return this.commands
|
const chainGroups = computed(() => getChainGroups(props.commands));
|
||||||
.filter(
|
|
||||||
(cmd) => cmd.chainId && cmd.commandType === cmd.commandChain?.[0]
|
|
||||||
)
|
|
||||||
.map((cmd) => cmd.chainId)
|
|
||||||
.filter((chainId, index, self) => self.indexOf(chainId) === index);
|
|
||||||
},
|
|
||||||
chainGroups() {
|
|
||||||
const groups = [];
|
|
||||||
const activeGroups = new Map(); // 用于跟踪活动的组
|
|
||||||
|
|
||||||
this.commands.forEach((cmd, index) => {
|
// 使用 watchEffect 监听 commands 变化并重新计算样式
|
||||||
if (cmd.chainId) {
|
watchEffect(() => {
|
||||||
if (cmd.commandType === cmd.commandChain?.[0]) {
|
// 如果 commands 为空,不生成样式
|
||||||
// 开始一个新的组
|
if (!props.commands?.length) {
|
||||||
activeGroups.set(cmd.chainId, {
|
styles.value = "";
|
||||||
chainId: cmd.chainId,
|
return;
|
||||||
startIndex: index,
|
}
|
||||||
});
|
|
||||||
} else if (
|
try {
|
||||||
cmd.commandType === cmd.commandChain?.[cmd.commandChain.length - 1]
|
// 1. 获取唯一的链ID
|
||||||
) {
|
const uniqueChainIds = [
|
||||||
// 结束一个组
|
...new Set(
|
||||||
const group = activeGroups.get(cmd.chainId);
|
props.commands
|
||||||
if (group) {
|
.filter(
|
||||||
group.endIndex = index;
|
(cmd) =>
|
||||||
groups.push({ ...group });
|
cmd.chainId && cmd.commandType === cmd.commandChain?.[0]
|
||||||
activeGroups.delete(cmd.chainId);
|
)
|
||||||
|
.map((cmd) => cmd.chainId)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 2. 使用计算好的链组
|
||||||
|
const groups = chainGroups.value;
|
||||||
|
|
||||||
|
// 3. 生成样式
|
||||||
|
const styleRules = {};
|
||||||
|
const { hueStep, indent, lightSl, darkSl } = STYLE_CONSTANTS;
|
||||||
|
|
||||||
|
uniqueChainIds.forEach((chainId, index) => {
|
||||||
|
const hue = (index * hueStep) % 360;
|
||||||
|
const className = "chain-group-" + chainId;
|
||||||
|
|
||||||
|
// 计算深度
|
||||||
|
const currentGroup = groups.find((g) => g.chainId === chainId);
|
||||||
|
if (!currentGroup) return;
|
||||||
|
|
||||||
|
let depth = 1;
|
||||||
|
const parents = [];
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
if (
|
||||||
|
group.startIndex < currentGroup.startIndex &&
|
||||||
|
group.endIndex > currentGroup.endIndex
|
||||||
|
) {
|
||||||
|
depth++;
|
||||||
|
parents.push(group.chainId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shadows = generateShadows(
|
||||||
|
parents,
|
||||||
|
hue,
|
||||||
|
indent,
|
||||||
|
lightSl,
|
||||||
|
darkSl,
|
||||||
|
uniqueChainIds
|
||||||
|
);
|
||||||
|
|
||||||
|
const commonStyle = {
|
||||||
|
marginLeft: `calc(${indent}px * ${depth - 1})`,
|
||||||
|
};
|
||||||
|
|
||||||
|
styleRules["." + className] = {
|
||||||
|
...commonStyle,
|
||||||
|
boxShadow: shadows.light.join(", "),
|
||||||
|
};
|
||||||
|
|
||||||
|
styleRules[".body--dark ." + className] = {
|
||||||
|
...commonStyle,
|
||||||
|
boxShadow: shadows.dark.join(", "),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 生成最终的样式字符串
|
||||||
|
styles.value = Object.entries(styleRules)
|
||||||
|
.map(([selector, rules]) => generateStyleString(selector, rules))
|
||||||
|
.join("\n\n");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error generating chain styles:", error);
|
||||||
|
styles.value = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
styles,
|
||||||
|
getChainGroupClass(index) {
|
||||||
|
const classes = {};
|
||||||
|
|
||||||
|
// 使用计算好的链组
|
||||||
|
for (const group of chainGroups.value) {
|
||||||
|
if (index >= group.startIndex && index <= group.endIndex) {
|
||||||
|
classes[`chain-group-${group.chainId}`] = true;
|
||||||
|
|
||||||
|
if (index === group.startIndex) {
|
||||||
|
classes["chain-start"] = true;
|
||||||
|
} else if (index === group.endIndex) {
|
||||||
|
classes["chain-end"] = true;
|
||||||
|
} else {
|
||||||
|
classes["chain-middle"] = true;
|
||||||
|
//清除嵌套控制流程的冲突样式
|
||||||
|
classes["chain-start"] = false;
|
||||||
|
classes["chain-end"] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return groups;
|
return classes;
|
||||||
},
|
},
|
||||||
styleConstants() {
|
};
|
||||||
return {
|
|
||||||
goldenRatio: 0.618033988749895,
|
|
||||||
hueStep: 360 * 0.618033988749895,
|
|
||||||
indent: 5,
|
|
||||||
lightSl: "60%, 60%",
|
|
||||||
darkSl: "60%, 40%",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
chainStylesMap() {
|
|
||||||
const styles = {};
|
|
||||||
const { hueStep, indent, lightSl, darkSl } = this.styleConstants;
|
|
||||||
|
|
||||||
// 先添加圆角样式规则
|
|
||||||
styles[".chain-start"] = { borderRadius: "4px 4px 0 0" };
|
|
||||||
styles[".chain-end"] = { borderRadius: "0 4px 0 4px" };
|
|
||||||
styles[".chain-middle"] = { borderRadius: "0 4px 0 0" };
|
|
||||||
|
|
||||||
this.uniqueChainIds.forEach((chainId, index) => {
|
|
||||||
const hue = (index * hueStep) % 360;
|
|
||||||
const className = "chain-group-" + chainId;
|
|
||||||
const depth = this.getChainDepth(chainId);
|
|
||||||
const parentChainIds = this.getParentChainIds(chainId);
|
|
||||||
|
|
||||||
// 生成阴影
|
|
||||||
const shadows = parentChainIds.reduce(
|
|
||||||
(acc, parentChainId, i) => {
|
|
||||||
const parentIndex = this.uniqueChainIds.indexOf(parentChainId);
|
|
||||||
const parentHue = (parentIndex * hueStep) % 360;
|
|
||||||
const start = -((i + 2) * indent);
|
|
||||||
acc.light.push(
|
|
||||||
start + "px 0 0 0 hsl(" + parentHue + ", " + lightSl + ")"
|
|
||||||
);
|
|
||||||
acc.dark.push(
|
|
||||||
start + "px 0 0 0 hsl(" + parentHue + ", " + darkSl + ")"
|
|
||||||
);
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
light: [-indent + "px 0 0 0 hsl(" + hue + ", " + lightSl + ")"],
|
|
||||||
dark: [-indent + "px 0 0 0 hsl(" + hue + ", " + darkSl + ")"],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 基础样式
|
|
||||||
const commonStyle = {
|
|
||||||
marginLeft: "calc(" + indent + "px * " + (depth - 1) + ")",
|
|
||||||
};
|
|
||||||
|
|
||||||
// 生成样式规则
|
|
||||||
styles["." + className] = {
|
|
||||||
...commonStyle,
|
|
||||||
background: "hsla(" + hue + ", " + lightSl + ", 0.15)",
|
|
||||||
boxShadow: shadows.light.join(", "),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 暗色模式样式
|
|
||||||
styles[".body--dark ." + className] = {
|
|
||||||
...commonStyle,
|
|
||||||
background: "hsla(" + hue + ", " + darkSl + ", 0.2)",
|
|
||||||
boxShadow: shadows.dark.join(", "),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return styles;
|
|
||||||
},
|
|
||||||
chainStyles() {
|
|
||||||
return Object.entries(this.chainStylesMap)
|
|
||||||
.map(
|
|
||||||
([selector, rules]) =>
|
|
||||||
selector +
|
|
||||||
" {\n" +
|
|
||||||
Object.entries(rules)
|
|
||||||
.map(
|
|
||||||
([prop, value]) =>
|
|
||||||
" " +
|
|
||||||
prop.replace(/([A-Z])/g, "-$1").toLowerCase() +
|
|
||||||
": " +
|
|
||||||
value +
|
|
||||||
" !important;"
|
|
||||||
)
|
|
||||||
.join("\n") +
|
|
||||||
"\n}"
|
|
||||||
)
|
|
||||||
.join("\n\n");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getChainDepth(chainId) {
|
|
||||||
let depth = 1;
|
|
||||||
let currentIndex = this.commands.findIndex(
|
|
||||||
(cmd) =>
|
|
||||||
cmd.chainId === chainId && cmd.commandType === cmd.commandChain?.[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (currentIndex === -1) return depth;
|
|
||||||
|
|
||||||
for (let i = 0; i < currentIndex; i++) {
|
|
||||||
const cmd = this.commands[i];
|
|
||||||
if (cmd.chainId && cmd.commandType === cmd.commandChain?.[0]) {
|
|
||||||
const endIndex = this.commands.findIndex(
|
|
||||||
(c, idx) =>
|
|
||||||
idx > i &&
|
|
||||||
c.chainId === cmd.chainId &&
|
|
||||||
c.commandType === cmd.commandChain?.[cmd.commandChain.length - 1]
|
|
||||||
);
|
|
||||||
if (endIndex > currentIndex) {
|
|
||||||
depth++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return depth;
|
|
||||||
},
|
|
||||||
getParentChainIds(chainId) {
|
|
||||||
const parents = [];
|
|
||||||
let currentIndex = this.commands.findIndex(
|
|
||||||
(cmd) =>
|
|
||||||
cmd.chainId === chainId && cmd.commandType === cmd.commandChain?.[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (currentIndex === -1) return parents;
|
|
||||||
|
|
||||||
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
||||||
const cmd = this.commands[i];
|
|
||||||
if (cmd.chainId && cmd.commandType === cmd.commandChain?.[0]) {
|
|
||||||
const endIndex = this.commands.findIndex(
|
|
||||||
(c, idx) =>
|
|
||||||
idx > i &&
|
|
||||||
c.chainId === cmd.chainId &&
|
|
||||||
c.commandType === cmd.commandChain?.[cmd.commandChain.length - 1]
|
|
||||||
);
|
|
||||||
if (endIndex > currentIndex) {
|
|
||||||
parents.push(cmd.chainId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parents;
|
|
||||||
},
|
|
||||||
getChainGroupClass(index) {
|
|
||||||
// 找出所有包含当前索引的组
|
|
||||||
const matchingGroups = this.chainGroups.filter(
|
|
||||||
(g) => index >= g.startIndex && index <= g.endIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
// 返回所有匹配组的类名
|
|
||||||
const classes = {};
|
|
||||||
matchingGroups.forEach((group) => {
|
|
||||||
classes["chain-group-" + group.chainId] = true;
|
|
||||||
if (index === group.startIndex) {
|
|
||||||
classes["chain-start"] = true;
|
|
||||||
}
|
|
||||||
if (index === group.endIndex) {
|
|
||||||
classes["chain-end"] = true;
|
|
||||||
}
|
|
||||||
if (index > group.startIndex && index < group.endIndex) {
|
|
||||||
classes["chain-middle"] = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return classes;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,33 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="composer-buttons">
|
<div class="composer-buttons">
|
||||||
<q-btn
|
<div class="left-buttons">
|
||||||
@click="$q.dark.toggle()"
|
<q-btn
|
||||||
:icon="$q.dark.isActive ? 'dark_mode' : 'light_mode'"
|
:icon="isAllCollapsed ? 'unfold_more' : 'unfold_less'"
|
||||||
flat
|
dense
|
||||||
dense
|
flat
|
||||||
v-if="isDev"
|
@click="$emit('action', isAllCollapsed ? 'expandAll' : 'collapseAll')"
|
||||||
>
|
>
|
||||||
</q-btn>
|
<q-tooltip>{{ isAllCollapsed ? "展开所有" : "折叠所有" }}</q-tooltip>
|
||||||
<q-btn icon="logout" dense flat v-close-popup>
|
</q-btn>
|
||||||
<q-tooltip>退出可视化编排</q-tooltip>
|
</div>
|
||||||
</q-btn>
|
|
||||||
<q-btn dense icon="publish" flat @click="$emit('action', 'insert')">
|
<div class="right-buttons">
|
||||||
<q-tooltip>插入到编辑器光标处</q-tooltip>
|
<q-btn
|
||||||
</q-btn>
|
@click="$q.dark.toggle()"
|
||||||
<q-btn dense flat icon="done_all" @click="$emit('action', 'apply')">
|
:icon="$q.dark.isActive ? 'dark_mode' : 'light_mode'"
|
||||||
<q-tooltip>清空编辑器内容并插入</q-tooltip>
|
flat
|
||||||
</q-btn>
|
dense
|
||||||
<q-btn
|
v-if="isDev"
|
||||||
flat
|
>
|
||||||
dense
|
</q-btn>
|
||||||
icon="preview"
|
<q-btn icon="logout" dense flat v-close-popup>
|
||||||
@mouseenter="handleMouseEnter"
|
<q-tooltip>退出可视化编排</q-tooltip>
|
||||||
@mouseleave="handleMouseLeave"
|
</q-btn>
|
||||||
>
|
<q-btn dense icon="publish" flat @click="$emit('action', 'insert')">
|
||||||
</q-btn>
|
<q-tooltip>插入到编辑器光标处</q-tooltip>
|
||||||
<q-btn dense flat icon="play_circle" @click="$emit('action', 'run')">
|
</q-btn>
|
||||||
<q-tooltip>运行</q-tooltip>
|
<q-btn dense flat icon="done_all" @click="$emit('action', 'apply')">
|
||||||
</q-btn>
|
<q-tooltip>清空编辑器内容并插入</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
icon="preview"
|
||||||
|
@mouseenter="handleMouseEnter"
|
||||||
|
@mouseleave="handleMouseLeave"
|
||||||
|
>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn dense flat icon="play_circle" @click="$emit('action', 'run')">
|
||||||
|
<q-tooltip>运行</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
<transition name="preview-fade">
|
<transition name="preview-fade">
|
||||||
<div v-if="isVisible" class="preview-popup">
|
<div v-if="isVisible" class="preview-popup">
|
||||||
@ -52,6 +65,10 @@ export default defineComponent({
|
|||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
isAllCollapsed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ["action"],
|
emits: ["action"],
|
||||||
@ -88,15 +105,25 @@ export default defineComponent({
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.composer-buttons {
|
.composer-buttons {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer-buttons > .q-btn {
|
.left-buttons,
|
||||||
|
.right-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-buttons > div > .q-btn {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer-buttons > .q-btn:hover {
|
.composer-buttons > div > .q-btn:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
color: var(--q-primary);
|
color: var(--q-primary);
|
Loading…
x
Reference in New Issue
Block a user