mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-07 21:46:12 +08:00
条件判断添加作用范围样式及拖拽校验
This commit is contained in:
parent
9ea10f8033
commit
f3a01a1ba9
@ -251,6 +251,13 @@ export default defineComponent({
|
||||
.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: {
|
||||
// 监听 glassEffect 值变化
|
||||
|
@ -1,9 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
class="composer-card q-pa-xs"
|
||||
:class="{ 'can-drop': canDrop }"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<div class="composer-card" :class="{ 'can-drop': canDrop }" v-bind="$attrs">
|
||||
<q-card class="command-item">
|
||||
<q-card-section class="q-pa-sm">
|
||||
<CommandHead
|
||||
@ -21,8 +17,8 @@
|
||||
v-model="argvLocal"
|
||||
:command="command"
|
||||
v-bind="command.componentProps || {}"
|
||||
:type="command.controlFlowType"
|
||||
@addBranch="$emit('addBranch')"
|
||||
:type="command.commandType"
|
||||
@addBranch="(chainInfo) => $emit('addBranch', chainInfo)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@ -273,6 +269,7 @@ export default defineComponent({
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transform-origin: center;
|
||||
opacity: 1;
|
||||
padding: 2px 2px 2px 0;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="composer-flow">
|
||||
<ChainStyles ref="chainStyles" :commands="commands" />
|
||||
<div class="section-header">
|
||||
<q-icon name="timeline" size="20px" class="q-mx-sm text-primary" />
|
||||
<span class="text-subtitle1">命令流程</span>
|
||||
@ -18,7 +19,7 @@
|
||||
@dragleave.prevent="onDragLeave"
|
||||
>
|
||||
<draggable
|
||||
v-model="commands"
|
||||
:list="commands"
|
||||
group="commands"
|
||||
item-key="id"
|
||||
class="flow-list"
|
||||
@ -26,6 +27,7 @@
|
||||
:animation="200"
|
||||
@start="onDragStart"
|
||||
@end="onDragEnd"
|
||||
@change="onDragChange"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<transition name="slide-fade" mode="out-in" appear>
|
||||
@ -37,6 +39,7 @@
|
||||
'insert-after':
|
||||
dragIndex === commands.length &&
|
||||
index === commands.length - 1,
|
||||
...getChainGroupClass(index),
|
||||
}"
|
||||
>
|
||||
<ComposerCard
|
||||
@ -47,21 +50,14 @@
|
||||
@update:argv="(val) => handleArgvChange(index, val)"
|
||||
@update:command="(val) => updateCommand(index, val)"
|
||||
@run="handleRunCommand"
|
||||
@add-branch="() => addBranch(index)"
|
||||
@add-branch="(chainInfo) => addBranch(index, chainInfo)"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
</draggable>
|
||||
<div v-if="commands.length === 0" 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>
|
||||
<div v-else class="drop-area">
|
||||
<q-icon name="add" size="32px" />
|
||||
</div>
|
||||
<EmptyFlow v-if="commands.length === 0" />
|
||||
<DropArea v-else />
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
@ -72,6 +68,9 @@ import { defineComponent, inject } from "vue";
|
||||
import draggable from "vuedraggable";
|
||||
import ComposerCard from "./ComposerCard.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({
|
||||
name: "ComposerFlow",
|
||||
@ -79,6 +78,9 @@ export default defineComponent({
|
||||
draggable,
|
||||
ComposerCard,
|
||||
ComposerButtons,
|
||||
ChainStyles,
|
||||
EmptyFlow,
|
||||
DropArea,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
@ -91,6 +93,17 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue", "add-command", "action"],
|
||||
setup() {
|
||||
const removeVariable = inject("removeVariable");
|
||||
return { removeVariable };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragIndex: -1,
|
||||
isDragging: false,
|
||||
draggedCommand: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
commands: {
|
||||
get() {
|
||||
@ -101,32 +114,46 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const removeVariable = inject("removeVariable");
|
||||
|
||||
return {
|
||||
removeVariable,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragIndex: -1,
|
||||
isDragging: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onDragStart() {
|
||||
this.isDragging = true;
|
||||
getChainGroupClass(index) {
|
||||
return this.$refs.chainStyles?.getChainGroupClass(index) || {};
|
||||
},
|
||||
getPlaceholder(element, index) {
|
||||
return element.desc;
|
||||
},
|
||||
onDragStart(event) {
|
||||
this.isDragging = true;
|
||||
this.draggedCommand = this.commands[event.oldIndex];
|
||||
},
|
||||
|
||||
onDragEnd() {
|
||||
this.isDragging = false;
|
||||
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) {
|
||||
if (!this.isDragging) {
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const items = this.$el.querySelectorAll(".flow-item");
|
||||
const mouseY = event.clientY;
|
||||
|
||||
@ -154,59 +181,86 @@ export default defineComponent({
|
||||
this.dragIndex = closestIndex;
|
||||
}
|
||||
},
|
||||
|
||||
onDragLeave() {
|
||||
if (!this.isDragging) {
|
||||
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) {
|
||||
try {
|
||||
const actionData = event.dataTransfer.getData("action");
|
||||
if (!actionData) return;
|
||||
|
||||
const parsedAction = JSON.parse(actionData);
|
||||
const isControlFlow = parsedAction.isControlFlow;
|
||||
const commandChain = parsedAction.commandChain;
|
||||
const newCommand = this.createNewCommand(parsedAction);
|
||||
let newCommands = [...this.commands];
|
||||
|
||||
const newCommand = {
|
||||
...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 (!commandChain) {
|
||||
// 处理单个命令
|
||||
if (this.dragIndex >= 0) {
|
||||
newCommands.splice(this.dragIndex, 0, newCommand);
|
||||
} else {
|
||||
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);
|
||||
@ -215,6 +269,21 @@ export default defineComponent({
|
||||
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) {
|
||||
const command = this.commands[index];
|
||||
// 如果命令有输出变量,需要先清理
|
||||
@ -225,9 +294,6 @@ export default defineComponent({
|
||||
newCommands.splice(index, 1);
|
||||
this.$emit("update:modelValue", newCommands);
|
||||
},
|
||||
getPlaceholder(element, index) {
|
||||
return element.desc;
|
||||
},
|
||||
toggleSaveOutput(index) {
|
||||
const newCommands = [...this.commands];
|
||||
newCommands[index].saveOutput = !newCommands[index].saveOutput;
|
||||
@ -268,27 +334,27 @@ export default defineComponent({
|
||||
// 触发运行事件
|
||||
this.$emit("action", "run", tempFlow);
|
||||
},
|
||||
addBranch(index) {
|
||||
addBranch(index, chainInfo) {
|
||||
const newCommands = [...this.commands];
|
||||
const midCommand = {
|
||||
const branchCommand = {
|
||||
...newCommands[index],
|
||||
id: Date.now(),
|
||||
controlFlowType: "mid",
|
||||
id: this.getUniqueId(),
|
||||
chainId: chainInfo.chainId,
|
||||
commandType: chainInfo.commandType,
|
||||
argv: "",
|
||||
};
|
||||
|
||||
// 找到对应的end位置
|
||||
let endIndex = index + 1;
|
||||
let depth = 1;
|
||||
while (endIndex < newCommands.length && depth > 0) {
|
||||
if (newCommands[endIndex].controlFlowType === "start") depth++;
|
||||
if (newCommands[endIndex].controlFlowType === "end") depth--;
|
||||
endIndex++;
|
||||
// 找到对应的 chainId 的最后一个命令位置
|
||||
let lastIndex = -1;
|
||||
for (let i = index + 1; i < newCommands.length; i++) {
|
||||
if (newCommands[i].chainId === chainInfo.chainId) {
|
||||
lastIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// 在end之前插入新的分支
|
||||
if (endIndex > index + 1) {
|
||||
newCommands.splice(endIndex - 1, 0, midCommand);
|
||||
// 在最后一个命令之前插入新的分支命令
|
||||
if (lastIndex !== -1) {
|
||||
newCommands.splice(lastIndex, 0, branchCommand);
|
||||
this.$emit("update:modelValue", newCommands);
|
||||
}
|
||||
},
|
||||
@ -333,53 +399,6 @@ export default defineComponent({
|
||||
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-leave-active {
|
||||
@ -460,23 +479,4 @@ export default defineComponent({
|
||||
.flow-item.insert-before + .flow-item {
|
||||
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>
|
||||
|
@ -179,6 +179,7 @@ export default defineComponent({
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
@ -218,6 +219,10 @@ export default defineComponent({
|
||||
border: none;
|
||||
}
|
||||
|
||||
.composer-list :deep(.q-expansion-item .q-item) {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.body--dark .composer-list {
|
||||
background-color: rgba(32, 32, 32, 0.8);
|
||||
}
|
||||
|
@ -3,30 +3,35 @@
|
||||
<div class="row items-center no-wrap">
|
||||
<!-- 类型标签 -->
|
||||
<div class="text-subtitle2 type-label">
|
||||
<template v-if="type === 'start'">如果满足</template>
|
||||
<template v-else-if="type === 'mid'">
|
||||
<template v-if="type === 'if'">如果满足</template>
|
||||
<template v-else-if="type === 'else'">
|
||||
{{ showCondition ? "否则满足" : "否则" }}
|
||||
</template>
|
||||
<template v-else>结束条件判断</template>
|
||||
</div>
|
||||
|
||||
<!-- start类型显示添加按钮 -->
|
||||
<!-- if类型显示添加按钮 -->
|
||||
<q-btn
|
||||
v-if="type === 'start'"
|
||||
v-if="type === 'if'"
|
||||
flat
|
||||
round
|
||||
dense
|
||||
size="sm"
|
||||
icon="add"
|
||||
class="control-btn q-mx-xs"
|
||||
@click="$emit('addBranch')"
|
||||
@click="
|
||||
$emit('addBranch', {
|
||||
chainId: command.chainId,
|
||||
commandType: 'else',
|
||||
})
|
||||
"
|
||||
>
|
||||
<q-tooltip>添加条件分支</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<!-- mid类型显示切换按钮 -->
|
||||
<q-btn
|
||||
v-if="type === 'mid'"
|
||||
v-if="type === 'else'"
|
||||
flat
|
||||
round
|
||||
dense
|
||||
@ -63,7 +68,7 @@ export default defineComponent({
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value) => ["start", "mid", "end"].includes(value),
|
||||
validator: (value) => ["if", "else", "end"].includes(value),
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue", "addBranch"],
|
||||
@ -80,7 +85,7 @@ export default defineComponent({
|
||||
computed: {
|
||||
showCondition() {
|
||||
return (
|
||||
this.type === "start" || (this.type === "mid" && this.showMidCondition)
|
||||
this.type === "if" || (this.type === "else" && this.showMidCondition)
|
||||
);
|
||||
},
|
||||
conditionLocal: {
|
||||
@ -94,9 +99,9 @@ export default defineComponent({
|
||||
},
|
||||
generatedCode() {
|
||||
switch (this.type) {
|
||||
case "start":
|
||||
case "if":
|
||||
return `if(${this.condition || "true"}){`;
|
||||
case "mid":
|
||||
case "else":
|
||||
return this.showMidCondition && this.condition
|
||||
? `}else if(${this.condition}){`
|
||||
: "}else{";
|
||||
@ -130,12 +135,12 @@ export default defineComponent({
|
||||
},
|
||||
parseCodeString(val) {
|
||||
try {
|
||||
if (this.type === "start") {
|
||||
if (this.type === "if") {
|
||||
const match = val.match(/^if\((.*)\){$/);
|
||||
if (match) {
|
||||
this.condition = match[1] === "true" ? "" : match[1];
|
||||
}
|
||||
} else if (this.type === "mid") {
|
||||
} else if (this.type === "else") {
|
||||
if (val === "}else{") {
|
||||
this.showMidCondition = false;
|
||||
this.condition = "";
|
||||
@ -165,6 +170,7 @@ export default defineComponent({
|
||||
color: var(--q-primary);
|
||||
white-space: nowrap;
|
||||
opacity: 0.9;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.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,
|
||||
{
|
||||
...action,
|
||||
id: Date.now(),
|
||||
id: this.$root.getUniqueId(),
|
||||
argv: "",
|
||||
saveOutput: false,
|
||||
useOutput: null,
|
||||
|
@ -7,6 +7,7 @@ export const controlCommands = {
|
||||
label: "条件判断",
|
||||
component: "ConditionalJudgment",
|
||||
isControlFlow: true,
|
||||
commandChain: ["if", "end"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user