编排新增循环

This commit is contained in:
fofolee 2025-01-02 01:09:18 +08:00
parent 37ebebc5ac
commit 620f42d64b
7 changed files with 294 additions and 51 deletions

View File

@ -107,6 +107,9 @@ export default defineComponent({
ConditionalJudgment: defineAsyncComponent(() =>
import("components/composer/control/ConditionalJudgment.vue")
),
LoopControl: defineAsyncComponent(() =>
import("components/composer/control/LoopControl.vue")
),
},
props: {
command: {

View File

@ -194,45 +194,32 @@ export default defineComponent({
}
},
checkAllChainOrders(commands) {
// chainId
const chainIds = new Set(
commands.filter((cmd) => cmd.chainId).map((cmd) => cmd.chainId)
);
// chainId
const chainGroups = commands.reduce((groups, cmd) => {
if (cmd.chainId) {
if (!groups[cmd.chainId]) {
groups[cmd.chainId] = [];
}
groups[cmd.chainId].push(cmd);
}
return groups;
}, {});
// true
if (Object.keys(chainGroups).length === 0) return true;
//
for (const chainId of chainIds) {
//
const indices = commands
.map((cmd, index) => ({ cmd, index }))
.filter((item) => item.cmd.chainId === chainId)
.map((item) => item.index);
// ifelseend
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 Object.values(chainGroups).every((chainCommands) => {
const commandChain = chainCommands[0].commandChain;
const firstCommand = chainCommands[0];
const lastCommand = chainCommands[chainCommands.length - 1];
// chainchainCommands
if (firstCommand.commandType !== commandChain[0]) return false;
// chainCommands
if (lastCommand.commandType !== commandChain[commandChain.length - 1])
return false;
}
return true;
return true;
});
},
onDrop(event) {
try {

View File

@ -1,7 +1,7 @@
<template>
<div class="row items-center">
<!-- 折叠按钮 -->
<div class="collapse-btn" v-if="!isLastCommandInChain">
<div class="collapse-btn" v-if="!isControlFlow || isFirstCommandInChain">
<q-btn
:icon="isCollapsed ? 'expand_more' : 'expand_less'"
dense
@ -12,8 +12,11 @@
<q-tooltip>折叠/展开此{{ isControlFlow ? "流程" : "命令" }}</q-tooltip>
</q-btn>
</div>
<div v-else class="end-icon">
<q-icon name="last_page" size="xs" />
<div v-else-if="isLastCommandInChain" class="chain-icon">
<q-icon name="linear_scale" size="xs" />
</div>
<div v-else class="chain-icon">
<q-icon name="fork_left" size="xs" />
</div>
<!-- 标题 -->
@ -93,15 +96,19 @@ export default {
<style scoped>
.collapse-btn,
.end-icon {
.chain-icon {
display: flex;
align-items: center;
padding-right: 4px;
transition: all 0.2s ease;
}
.chain-icon {
margin-right: 4px;
}
.collapse-btn :deep(.q-btn),
.end-icon {
.chain-icon {
opacity: 0.6;
min-height: 20px;
padding: 0 4px;

View File

@ -2,7 +2,7 @@
<div class="conditional-judgment">
<div class="row items-end no-wrap">
<!-- 类型标签 -->
<div class="text-subtitle2 type-label">
<div class="control-type-label">
<template v-if="type === 'if'">如果满足</template>
<template v-else-if="type === 'else'">
{{ showCondition ? "否则满足" : "否则" }}
@ -158,9 +158,8 @@ export default defineComponent({
</script>
<style scoped>
.type-label {
.control-type-label {
font-size: 14px;
color: var(--q-primary);
white-space: nowrap;
opacity: 0.9;
user-select: none;
@ -193,11 +192,6 @@ export default defineComponent({
transform: scale(1.1);
}
.body--dark .type-label {
color: var(--q-primary);
opacity: 0.8;
}
.body--dark .control-btn {
color: rgba(255, 255, 255, 0.7);
}

View File

@ -0,0 +1,237 @@
<template>
<div class="loop-control-wrapper" v-bind="$attrs">
<div class="loop-control row">
<!-- 类型标签和按钮区域 -->
<div class="control-type-label">
<template v-if="type === 'loop'">循环</template>
<template v-else-if="type === 'continue'">继续循环</template>
<template v-else-if="type === 'break'">终止循环</template>
<template v-else>结束循环</template>
</div>
<!-- 只在循环开始时显示添加按钮 -->
<q-btn-dropdown
v-if="type === 'loop'"
flat
dense
dropdown-icon="add"
no-icon-animation
size="sm"
class="control-btn"
>
<q-list>
<q-item
clickable
v-close-popup
@click="
$emit('addBranch', {
chainId: command.chainId,
commandType: 'continue',
})
"
>
<q-item-section>
<q-item-label>添加继续循环</q-item-label>
</q-item-section>
</q-item>
<q-item
clickable
v-close-popup
@click="
$emit('addBranch', {
chainId: command.chainId,
commandType: 'break',
})
"
>
<q-item-section>
<q-item-label>添加终止循环</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<!-- 循环设置区域 -->
<div v-if="type === 'loop'" class="loop-settings">
<q-input
dense
borderless
v-model="indexVar"
:is-variable="true"
class="loop-input"
>
<template v-slot:prepend>
<q-badge class="loop-input-prepend">变量</q-badge>
</template>
</q-input>
<q-input dense borderless v-model="startValue" class="loop-input">
<template v-slot:prepend>
<q-badge class="loop-input-prepend"></q-badge>
</template>
</q-input>
<q-input dense borderless v-model="endValue" class="loop-input">
<template v-slot:prepend>
<q-badge class="loop-input-prepend"></q-badge>
</template>
</q-input>
<q-input dense borderless v-model="stepValue" class="loop-input">
<template v-slot:prepend>
<q-badge class="loop-input-prepend">步进</q-badge>
</template>
</q-input>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "LoopControl",
inheritAttrs: false,
props: {
modelValue: String,
command: Object,
type: {
type: String,
required: true,
validator: (value) =>
["loop", "continue", "break", "end"].includes(value),
},
},
emits: ["update:modelValue", "addBranch"],
data() {
return {
indexVar: "i",
startValue: 0,
endValue: 10,
stepValue: 1,
};
},
created() {
if (!this.modelValue) {
this.updateValue();
}
},
computed: {
generatedCode() {
switch (this.type) {
case "loop":
const index = this.indexVar || "i";
const start = this.startValue || 0;
const end = this.endValue || 10;
const step = this.stepValue || 1;
return `for(let ${index}=${start};${index}<${end};${index}+=${step}){`;
case "continue":
return "continue;";
case "break":
return "break;";
case "end":
return "}";
default:
return "";
}
},
},
watch: {
modelValue: {
immediate: true,
handler(val) {
if (val) {
this.parseCodeString(val);
}
},
},
indexVar() {
this.updateValue();
},
startValue() {
this.updateValue();
},
endValue() {
this.updateValue();
},
stepValue() {
this.updateValue();
},
},
methods: {
updateValue() {
this.$emit("update:modelValue", this.generatedCode);
},
parseCodeString(val) {
try {
if (this.type === "loop") {
const match = val.match(
/^for\(let\s+(\w+)=(\d+);(\w+)<(\d+);(\w+)\+=(\d+)\){$/
);
if (match) {
this.indexVar = match[1];
this.startValue = parseInt(match[2]);
this.endValue = parseInt(match[4]);
this.stepValue = parseInt(match[6]);
}
}
} catch (e) {
console.error("Failed to parse code string:", e);
}
},
},
});
</script>
<style scoped>
.loop-control-wrapper {
width: 100%;
}
.loop-control {
display: flex;
align-items: center;
gap: 4px;
}
.control-type-label {
font-size: 14px;
font-weight: 500;
white-space: nowrap;
opacity: 0.9;
user-select: none;
}
.control-btn {
width: 21px;
height: 21px;
opacity: 0.7;
}
.control-btn:hover {
opacity: 1;
transform: scale(1.1);
}
.loop-settings {
display: flex;
gap: 4px;
}
.loop-input :deep(.q-field__control),
.loop-input :deep(.q-field__native),
.loop-input :deep(.q-field__marginal) {
padding: 0 5px;
height: 21px !important;
min-height: 21px;
font-size: 13px;
max-width: 80px;
}
/* 暗色模式适配 */
.body--dark .control-btn {
color: rgba(255, 255, 255, 0.7);
}
.body--dark .control-btn:hover {
color: var(--q-primary);
}
</style>

View File

@ -10,8 +10,8 @@ const STYLE_CONSTANTS = {
goldenRatio: 0.618033988749895,
hueStep: 360 * 0.618033988749895,
indent: 5,
lightSl: "60%, 60%",
darkSl: "60%, 40%",
lightSl: "70%, 60%",
darkSl: "60%, 60%",
};
//
@ -158,10 +158,18 @@ export default defineComponent({
boxShadow: shadows.light.join(", "),
};
styleRules["." + className + " .control-type-label"] = {
color: `hsl(${hue}, ${lightSl})`,
};
styleRules[".body--dark ." + className] = {
...commonStyle,
boxShadow: shadows.dark.join(", "),
};
styleRules[".body--dark ." + className + " .control-type-label"] = {
color: `hsl(${hue}, ${darkSl})`,
};
});
// 4.

View File

@ -9,5 +9,12 @@ export const controlCommands = {
isControlFlow: true,
commandChain: ["if", "end"],
},
{
value: "loop",
label: "循环",
component: "LoopControl",
isControlFlow: true,
commandChain: ["loop", "end"],
},
],
};