mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-08 14:34:13 +08:00
条件判断添加作用范围样式及拖拽校验
This commit is contained in:
parent
9ea10f8033
commit
f3a01a1ba9
@ -251,6 +251,13 @@ export default defineComponent({
|
|||||||
.padStart(2, "0")
|
.padStart(2, "0")
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
getUniqueId() {
|
||||||
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||||
|
const r = (Math.random() * 16) | 0;
|
||||||
|
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
// 监听 glassEffect 值变化
|
// 监听 glassEffect 值变化
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="composer-card" :class="{ 'can-drop': canDrop }" v-bind="$attrs">
|
||||||
class="composer-card q-pa-xs"
|
|
||||||
:class="{ 'can-drop': canDrop }"
|
|
||||||
v-bind="$attrs"
|
|
||||||
>
|
|
||||||
<q-card class="command-item">
|
<q-card class="command-item">
|
||||||
<q-card-section class="q-pa-sm">
|
<q-card-section class="q-pa-sm">
|
||||||
<CommandHead
|
<CommandHead
|
||||||
@ -21,8 +17,8 @@
|
|||||||
v-model="argvLocal"
|
v-model="argvLocal"
|
||||||
:command="command"
|
:command="command"
|
||||||
v-bind="command.componentProps || {}"
|
v-bind="command.componentProps || {}"
|
||||||
:type="command.controlFlowType"
|
:type="command.commandType"
|
||||||
@addBranch="$emit('addBranch')"
|
@addBranch="(chainInfo) => $emit('addBranch', chainInfo)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -273,6 +269,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="composer-flow">
|
<div class="composer-flow">
|
||||||
|
<ChainStyles ref="chainStyles" :commands="commands" />
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<q-icon name="timeline" size="20px" class="q-mx-sm text-primary" />
|
<q-icon name="timeline" size="20px" class="q-mx-sm text-primary" />
|
||||||
<span class="text-subtitle1">命令流程</span>
|
<span class="text-subtitle1">命令流程</span>
|
||||||
@ -18,7 +19,7 @@
|
|||||||
@dragleave.prevent="onDragLeave"
|
@dragleave.prevent="onDragLeave"
|
||||||
>
|
>
|
||||||
<draggable
|
<draggable
|
||||||
v-model="commands"
|
:list="commands"
|
||||||
group="commands"
|
group="commands"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
class="flow-list"
|
class="flow-list"
|
||||||
@ -26,6 +27,7 @@
|
|||||||
:animation="200"
|
:animation="200"
|
||||||
@start="onDragStart"
|
@start="onDragStart"
|
||||||
@end="onDragEnd"
|
@end="onDragEnd"
|
||||||
|
@change="onDragChange"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<transition name="slide-fade" mode="out-in" appear>
|
<transition name="slide-fade" mode="out-in" appear>
|
||||||
@ -37,6 +39,7 @@
|
|||||||
'insert-after':
|
'insert-after':
|
||||||
dragIndex === commands.length &&
|
dragIndex === commands.length &&
|
||||||
index === commands.length - 1,
|
index === commands.length - 1,
|
||||||
|
...getChainGroupClass(index),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<ComposerCard
|
<ComposerCard
|
||||||
@ -47,21 +50,14 @@
|
|||||||
@update:argv="(val) => handleArgvChange(index, val)"
|
@update:argv="(val) => handleArgvChange(index, val)"
|
||||||
@update:command="(val) => updateCommand(index, val)"
|
@update:command="(val) => updateCommand(index, val)"
|
||||||
@run="handleRunCommand"
|
@run="handleRunCommand"
|
||||||
@add-branch="() => addBranch(index)"
|
@add-branch="(chainInfo) => addBranch(index, chainInfo)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
<div v-if="commands.length === 0" class="empty-flow">
|
<EmptyFlow v-if="commands.length === 0" />
|
||||||
<div class="text-center text-grey-6">
|
<DropArea v-else />
|
||||||
<q-icon name="drag_indicator" size="32px" />
|
|
||||||
<div class="text-body2 q-mt-sm">从左侧拖拽命令到这里开始编排</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="drop-area">
|
|
||||||
<q-icon name="add" size="32px" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</div>
|
</div>
|
||||||
@ -72,6 +68,9 @@ 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 "./ComposerButtons.vue";
|
||||||
|
import ChainStyles from "./flow/ChainStyles.vue";
|
||||||
|
import EmptyFlow from "./flow/EmptyFlow.vue";
|
||||||
|
import DropArea from "./flow/DropArea.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "ComposerFlow",
|
name: "ComposerFlow",
|
||||||
@ -79,6 +78,9 @@ export default defineComponent({
|
|||||||
draggable,
|
draggable,
|
||||||
ComposerCard,
|
ComposerCard,
|
||||||
ComposerButtons,
|
ComposerButtons,
|
||||||
|
ChainStyles,
|
||||||
|
EmptyFlow,
|
||||||
|
DropArea,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -91,6 +93,17 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ["update:modelValue", "add-command", "action"],
|
emits: ["update:modelValue", "add-command", "action"],
|
||||||
|
setup() {
|
||||||
|
const removeVariable = inject("removeVariable");
|
||||||
|
return { removeVariable };
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dragIndex: -1,
|
||||||
|
isDragging: false,
|
||||||
|
draggedCommand: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
commands: {
|
commands: {
|
||||||
get() {
|
get() {
|
||||||
@ -101,32 +114,46 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
|
||||||
const removeVariable = inject("removeVariable");
|
|
||||||
|
|
||||||
return {
|
|
||||||
removeVariable,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
dragIndex: -1,
|
|
||||||
isDragging: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
onDragStart() {
|
getChainGroupClass(index) {
|
||||||
this.isDragging = true;
|
return this.$refs.chainStyles?.getChainGroupClass(index) || {};
|
||||||
|
},
|
||||||
|
getPlaceholder(element, index) {
|
||||||
|
return element.desc;
|
||||||
|
},
|
||||||
|
onDragStart(event) {
|
||||||
|
this.isDragging = true;
|
||||||
|
this.draggedCommand = this.commands[event.oldIndex];
|
||||||
},
|
},
|
||||||
|
|
||||||
onDragEnd() {
|
onDragEnd() {
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
this.dragIndex = -1;
|
this.dragIndex = -1;
|
||||||
|
this.draggedCommand = null;
|
||||||
},
|
},
|
||||||
|
onDragChange(event) {
|
||||||
|
let newCommands = [...this.commands];
|
||||||
|
|
||||||
|
if (event.moved || event.added) {
|
||||||
|
// 检查所有链式命令的顺序
|
||||||
|
const isValidOrder = this.checkAllChainOrders(newCommands);
|
||||||
|
|
||||||
|
if (!isValidOrder) {
|
||||||
|
// 如果顺序无效,恢复原始状态
|
||||||
|
if (event.moved) {
|
||||||
|
const { oldIndex, newIndex } = event.moved;
|
||||||
|
const [item] = newCommands.splice(newIndex, 1);
|
||||||
|
newCommands.splice(oldIndex, 0, item);
|
||||||
|
} else if (event.added) {
|
||||||
|
const { newIndex } = event.added;
|
||||||
|
newCommands.splice(newIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit("update:modelValue", newCommands);
|
||||||
|
},
|
||||||
onDragOver(event) {
|
onDragOver(event) {
|
||||||
if (!this.isDragging) {
|
if (!this.isDragging) {
|
||||||
const rect = event.currentTarget.getBoundingClientRect();
|
|
||||||
const items = this.$el.querySelectorAll(".flow-item");
|
const items = this.$el.querySelectorAll(".flow-item");
|
||||||
const mouseY = event.clientY;
|
const mouseY = event.clientY;
|
||||||
|
|
||||||
@ -154,59 +181,86 @@ export default defineComponent({
|
|||||||
this.dragIndex = closestIndex;
|
this.dragIndex = closestIndex;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onDragLeave() {
|
onDragLeave() {
|
||||||
if (!this.isDragging) {
|
if (!this.isDragging) {
|
||||||
this.dragIndex = -1;
|
this.dragIndex = -1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
checkAllChainOrders(commands) {
|
||||||
|
// 获取所有不同的 chainId
|
||||||
|
const chainIds = new Set(
|
||||||
|
commands.filter((cmd) => cmd.chainId).map((cmd) => cmd.chainId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 检查每个链的命令顺序
|
||||||
|
for (const chainId of chainIds) {
|
||||||
|
// 获取当前链的所有命令的索引
|
||||||
|
const indices = commands
|
||||||
|
.map((cmd, index) => ({ cmd, index }))
|
||||||
|
.filter((item) => item.cmd.chainId === chainId)
|
||||||
|
.map((item) => item.index);
|
||||||
|
|
||||||
|
// 获取 if、else、end 的位置
|
||||||
|
const ifIndex = indices.find(
|
||||||
|
(index) => commands[index].commandType === "if"
|
||||||
|
);
|
||||||
|
const endIndex = indices.find(
|
||||||
|
(index) => commands[index].commandType === "end"
|
||||||
|
);
|
||||||
|
const elseIndices = indices.filter(
|
||||||
|
(index) =>
|
||||||
|
commands[index].commandType !== "if" &&
|
||||||
|
commands[index].commandType !== "end"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 验证顺序
|
||||||
|
// 1. 必须有 if 和 end
|
||||||
|
if (ifIndex === undefined || endIndex === undefined) return false;
|
||||||
|
// 2. if 必须在所有其他命令前面
|
||||||
|
if (indices.some((index) => index < ifIndex)) return false;
|
||||||
|
// 3. end 必须在所有其他命令后面
|
||||||
|
if (indices.some((index) => index > endIndex)) return false;
|
||||||
|
// 4. else 必须在 if 和 end 之间
|
||||||
|
if (elseIndices.some((index) => index < ifIndex || index > endIndex))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
onDrop(event) {
|
onDrop(event) {
|
||||||
try {
|
try {
|
||||||
const actionData = event.dataTransfer.getData("action");
|
const actionData = event.dataTransfer.getData("action");
|
||||||
if (!actionData) return;
|
if (!actionData) return;
|
||||||
|
|
||||||
const parsedAction = JSON.parse(actionData);
|
const parsedAction = JSON.parse(actionData);
|
||||||
const isControlFlow = parsedAction.isControlFlow;
|
const commandChain = parsedAction.commandChain;
|
||||||
|
const newCommand = this.createNewCommand(parsedAction);
|
||||||
|
let newCommands = [...this.commands];
|
||||||
|
|
||||||
const newCommand = {
|
if (!commandChain) {
|
||||||
...parsedAction,
|
// 处理单个命令
|
||||||
id: Date.now(),
|
|
||||||
argv: "",
|
|
||||||
saveOutput: false,
|
|
||||||
useOutput: null,
|
|
||||||
outputVariable: null,
|
|
||||||
cmd: parsedAction.value || parsedAction.cmd,
|
|
||||||
value: parsedAction.value || parsedAction.cmd,
|
|
||||||
};
|
|
||||||
|
|
||||||
const newCommands = [...this.commands];
|
|
||||||
|
|
||||||
// 如果是控制流程命令,添加start和end两个卡片
|
|
||||||
if (isControlFlow) {
|
|
||||||
const startCommand = {
|
|
||||||
...newCommand,
|
|
||||||
id: Date.now(),
|
|
||||||
controlFlowType: "start",
|
|
||||||
};
|
|
||||||
|
|
||||||
const endCommand = {
|
|
||||||
...newCommand,
|
|
||||||
id: Date.now() + 1,
|
|
||||||
controlFlowType: "end",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.dragIndex >= 0) {
|
|
||||||
newCommands.splice(this.dragIndex, 0, startCommand, endCommand);
|
|
||||||
} else {
|
|
||||||
newCommands.push(startCommand, endCommand);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.dragIndex >= 0) {
|
if (this.dragIndex >= 0) {
|
||||||
newCommands.splice(this.dragIndex, 0, newCommand);
|
newCommands.splice(this.dragIndex, 0, newCommand);
|
||||||
} else {
|
} else {
|
||||||
newCommands.push(newCommand);
|
newCommands.push(newCommand);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 处理链式命令
|
||||||
|
const chainId = this.getUniqueId();
|
||||||
|
let insertIndex =
|
||||||
|
this.dragIndex >= 0 ? this.dragIndex : newCommands.length;
|
||||||
|
|
||||||
|
// 按顺序插入命令
|
||||||
|
for (const commandType of commandChain) {
|
||||||
|
const commandItem = {
|
||||||
|
...newCommand,
|
||||||
|
id: this.getUniqueId(),
|
||||||
|
commandType,
|
||||||
|
chainId,
|
||||||
|
};
|
||||||
|
newCommands.splice(insertIndex, 0, commandItem);
|
||||||
|
insertIndex++; // 更新插入位置,确保命令按顺序排列
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit("update:modelValue", newCommands);
|
this.$emit("update:modelValue", newCommands);
|
||||||
@ -215,6 +269,21 @@ export default defineComponent({
|
|||||||
console.debug("Internal drag & drop reorder", error);
|
console.debug("Internal drag & drop reorder", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
createNewCommand(parsedAction) {
|
||||||
|
return {
|
||||||
|
...parsedAction,
|
||||||
|
id: this.getUniqueId(),
|
||||||
|
argv: "",
|
||||||
|
saveOutput: false,
|
||||||
|
useOutput: null,
|
||||||
|
outputVariable: null,
|
||||||
|
cmd: parsedAction.value || parsedAction.cmd,
|
||||||
|
value: parsedAction.value || parsedAction.cmd,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getUniqueId() {
|
||||||
|
return this.$root.getUniqueId();
|
||||||
|
},
|
||||||
removeCommand(index) {
|
removeCommand(index) {
|
||||||
const command = this.commands[index];
|
const command = this.commands[index];
|
||||||
// 如果命令有输出变量,需要先清理
|
// 如果命令有输出变量,需要先清理
|
||||||
@ -225,9 +294,6 @@ export default defineComponent({
|
|||||||
newCommands.splice(index, 1);
|
newCommands.splice(index, 1);
|
||||||
this.$emit("update:modelValue", newCommands);
|
this.$emit("update:modelValue", newCommands);
|
||||||
},
|
},
|
||||||
getPlaceholder(element, index) {
|
|
||||||
return element.desc;
|
|
||||||
},
|
|
||||||
toggleSaveOutput(index) {
|
toggleSaveOutput(index) {
|
||||||
const newCommands = [...this.commands];
|
const newCommands = [...this.commands];
|
||||||
newCommands[index].saveOutput = !newCommands[index].saveOutput;
|
newCommands[index].saveOutput = !newCommands[index].saveOutput;
|
||||||
@ -268,27 +334,27 @@ export default defineComponent({
|
|||||||
// 触发运行事件
|
// 触发运行事件
|
||||||
this.$emit("action", "run", tempFlow);
|
this.$emit("action", "run", tempFlow);
|
||||||
},
|
},
|
||||||
addBranch(index) {
|
addBranch(index, chainInfo) {
|
||||||
const newCommands = [...this.commands];
|
const newCommands = [...this.commands];
|
||||||
const midCommand = {
|
const branchCommand = {
|
||||||
...newCommands[index],
|
...newCommands[index],
|
||||||
id: Date.now(),
|
id: this.getUniqueId(),
|
||||||
controlFlowType: "mid",
|
chainId: chainInfo.chainId,
|
||||||
|
commandType: chainInfo.commandType,
|
||||||
argv: "",
|
argv: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 找到对应的end位置
|
// 找到对应的 chainId 的最后一个命令位置
|
||||||
let endIndex = index + 1;
|
let lastIndex = -1;
|
||||||
let depth = 1;
|
for (let i = index + 1; i < newCommands.length; i++) {
|
||||||
while (endIndex < newCommands.length && depth > 0) {
|
if (newCommands[i].chainId === chainInfo.chainId) {
|
||||||
if (newCommands[endIndex].controlFlowType === "start") depth++;
|
lastIndex = i;
|
||||||
if (newCommands[endIndex].controlFlowType === "end") depth--;
|
}
|
||||||
endIndex++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在end之前插入新的分支
|
// 在最后一个命令之前插入新的分支命令
|
||||||
if (endIndex > index + 1) {
|
if (lastIndex !== -1) {
|
||||||
newCommands.splice(endIndex - 1, 0, midCommand);
|
newCommands.splice(lastIndex, 0, branchCommand);
|
||||||
this.$emit("update:modelValue", newCommands);
|
this.$emit("update:modelValue", newCommands);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -333,53 +399,6 @@ export default defineComponent({
|
|||||||
background-color: rgba(32, 32, 32, 0.8);
|
background-color: rgba(32, 32, 32, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .flow-list {
|
|
||||||
min-height: 50px;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.drop-area {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 50px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #9994;
|
|
||||||
border: 2px dashed #9994;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body--dark .drop-area {
|
|
||||||
color: #6664;
|
|
||||||
border: 2px dashed #6664;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-flow {
|
|
||||||
min-height: 200px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 2px dashed #e0e0e0;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 8px 0;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body--dark .empty-flow {
|
|
||||||
border: 2px dashed #676666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-flow:hover {
|
|
||||||
border-color: #bdbdbd;
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body--dark .empty-flow:hover {
|
|
||||||
border-color: #676666;
|
|
||||||
background-color: #303132;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滑动淡出动画 */
|
/* 滑动淡出动画 */
|
||||||
.slide-fade-enter-active,
|
.slide-fade-enter-active,
|
||||||
.slide-fade-leave-active {
|
.slide-fade-leave-active {
|
||||||
@ -460,23 +479,4 @@ export default defineComponent({
|
|||||||
.flow-item.insert-before + .flow-item {
|
.flow-item.insert-before + .flow-item {
|
||||||
transform: translateY(3px);
|
transform: translateY(3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 暗色模式适配 */
|
|
||||||
.body--dark .flow-item::before,
|
|
||||||
.body--dark .flow-item::after {
|
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
transparent,
|
|
||||||
rgba(255, 255, 255, 0.08) 10%,
|
|
||||||
rgba(255, 255, 255, 0.15) 50%,
|
|
||||||
rgba(255, 255, 255, 0.08) 90%,
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.03),
|
|
||||||
0 0 4px rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.body--dark .section-header {
|
|
||||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -179,6 +179,7 @@ export default defineComponent({
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
@ -218,6 +219,10 @@ export default defineComponent({
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.composer-list :deep(.q-expansion-item .q-item) {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
.body--dark .composer-list {
|
.body--dark .composer-list {
|
||||||
background-color: rgba(32, 32, 32, 0.8);
|
background-color: rgba(32, 32, 32, 0.8);
|
||||||
}
|
}
|
||||||
|
@ -3,30 +3,35 @@
|
|||||||
<div class="row items-center no-wrap">
|
<div class="row items-center no-wrap">
|
||||||
<!-- 类型标签 -->
|
<!-- 类型标签 -->
|
||||||
<div class="text-subtitle2 type-label">
|
<div class="text-subtitle2 type-label">
|
||||||
<template v-if="type === 'start'">如果满足</template>
|
<template v-if="type === 'if'">如果满足</template>
|
||||||
<template v-else-if="type === 'mid'">
|
<template v-else-if="type === 'else'">
|
||||||
{{ showCondition ? "否则满足" : "否则" }}
|
{{ showCondition ? "否则满足" : "否则" }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>结束条件判断</template>
|
<template v-else>结束条件判断</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- start类型显示添加按钮 -->
|
<!-- if类型显示添加按钮 -->
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="type === 'start'"
|
v-if="type === 'if'"
|
||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
dense
|
dense
|
||||||
size="sm"
|
size="sm"
|
||||||
icon="add"
|
icon="add"
|
||||||
class="control-btn q-mx-xs"
|
class="control-btn q-mx-xs"
|
||||||
@click="$emit('addBranch')"
|
@click="
|
||||||
|
$emit('addBranch', {
|
||||||
|
chainId: command.chainId,
|
||||||
|
commandType: 'else',
|
||||||
|
})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<q-tooltip>添加条件分支</q-tooltip>
|
<q-tooltip>添加条件分支</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
|
||||||
<!-- mid类型显示切换按钮 -->
|
<!-- mid类型显示切换按钮 -->
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="type === 'mid'"
|
v-if="type === 'else'"
|
||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
dense
|
dense
|
||||||
@ -63,7 +68,7 @@ export default defineComponent({
|
|||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
validator: (value) => ["start", "mid", "end"].includes(value),
|
validator: (value) => ["if", "else", "end"].includes(value),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ["update:modelValue", "addBranch"],
|
emits: ["update:modelValue", "addBranch"],
|
||||||
@ -80,7 +85,7 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
showCondition() {
|
showCondition() {
|
||||||
return (
|
return (
|
||||||
this.type === "start" || (this.type === "mid" && this.showMidCondition)
|
this.type === "if" || (this.type === "else" && this.showMidCondition)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
conditionLocal: {
|
conditionLocal: {
|
||||||
@ -94,9 +99,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
generatedCode() {
|
generatedCode() {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case "start":
|
case "if":
|
||||||
return `if(${this.condition || "true"}){`;
|
return `if(${this.condition || "true"}){`;
|
||||||
case "mid":
|
case "else":
|
||||||
return this.showMidCondition && this.condition
|
return this.showMidCondition && this.condition
|
||||||
? `}else if(${this.condition}){`
|
? `}else if(${this.condition}){`
|
||||||
: "}else{";
|
: "}else{";
|
||||||
@ -130,12 +135,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
parseCodeString(val) {
|
parseCodeString(val) {
|
||||||
try {
|
try {
|
||||||
if (this.type === "start") {
|
if (this.type === "if") {
|
||||||
const match = val.match(/^if\((.*)\){$/);
|
const match = val.match(/^if\((.*)\){$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
this.condition = match[1] === "true" ? "" : match[1];
|
this.condition = match[1] === "true" ? "" : match[1];
|
||||||
}
|
}
|
||||||
} else if (this.type === "mid") {
|
} else if (this.type === "else") {
|
||||||
if (val === "}else{") {
|
if (val === "}else{") {
|
||||||
this.showMidCondition = false;
|
this.showMidCondition = false;
|
||||||
this.condition = "";
|
this.condition = "";
|
||||||
@ -165,6 +170,7 @@ export default defineComponent({
|
|||||||
color: var(--q-primary);
|
color: var(--q-primary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.condition-input {
|
.condition-input {
|
||||||
|
216
src/components/composer/flow/ChainStyles.vue
Normal file
216
src/components/composer/flow/ChainStyles.vue
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="'style'" v-if="chainStyles">{{ chainStyles }}</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "ChainStyles",
|
||||||
|
props: {
|
||||||
|
commands: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
uniqueChainIds() {
|
||||||
|
return this.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) => {
|
||||||
|
if (cmd.chainId) {
|
||||||
|
if (cmd.commandType === cmd.commandChain?.[0]) {
|
||||||
|
// 开始一个新的组
|
||||||
|
activeGroups.set(cmd.chainId, {
|
||||||
|
chainId: cmd.chainId,
|
||||||
|
startIndex: index,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
cmd.commandType === cmd.commandChain?.[cmd.commandChain.length - 1]
|
||||||
|
) {
|
||||||
|
// 结束一个组
|
||||||
|
const group = activeGroups.get(cmd.chainId);
|
||||||
|
if (group) {
|
||||||
|
group.endIndex = index;
|
||||||
|
groups.push({ ...group });
|
||||||
|
activeGroups.delete(cmd.chainId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
},
|
||||||
|
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>
|
32
src/components/composer/flow/DropArea.vue
Normal file
32
src/components/composer/flow/DropArea.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<div class="drop-area">
|
||||||
|
<q-icon name="add" size="32px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "DropArea",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.drop-area {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 50px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #9994;
|
||||||
|
border: 2px dashed #9994;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .drop-area {
|
||||||
|
color: #6664;
|
||||||
|
border: 2px dashed #6664;
|
||||||
|
}
|
||||||
|
</style>
|
44
src/components/composer/flow/EmptyFlow.vue
Normal file
44
src/components/composer/flow/EmptyFlow.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="empty-flow">
|
||||||
|
<div class="text-center text-grey-6">
|
||||||
|
<q-icon name="drag_indicator" size="32px" />
|
||||||
|
<div class="text-body2 q-mt-sm">从左侧拖拽命令到这里开始编排</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "EmptyFlow",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.empty-flow {
|
||||||
|
min-height: 200px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px dashed #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 8px 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .empty-flow {
|
||||||
|
border: 2px dashed #676666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-flow:hover {
|
||||||
|
border-color: #bdbdbd;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .empty-flow:hover {
|
||||||
|
border-color: #676666;
|
||||||
|
background-color: #303132;
|
||||||
|
}
|
||||||
|
</style>
|
@ -133,7 +133,7 @@ export default defineComponent({
|
|||||||
...this.selectedActions,
|
...this.selectedActions,
|
||||||
{
|
{
|
||||||
...action,
|
...action,
|
||||||
id: Date.now(),
|
id: this.$root.getUniqueId(),
|
||||||
argv: "",
|
argv: "",
|
||||||
saveOutput: false,
|
saveOutput: false,
|
||||||
useOutput: null,
|
useOutput: null,
|
||||||
|
@ -7,6 +7,7 @@ export const controlCommands = {
|
|||||||
label: "条件判断",
|
label: "条件判断",
|
||||||
component: "ConditionalJudgment",
|
component: "ConditionalJudgment",
|
||||||
isControlFlow: true,
|
isControlFlow: true,
|
||||||
|
commandChain: ["if", "end"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user