mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-09 06:54:11 +08:00
编排新增循环
This commit is contained in:
parent
37ebebc5ac
commit
620f42d64b
@ -107,6 +107,9 @@ export default defineComponent({
|
|||||||
ConditionalJudgment: defineAsyncComponent(() =>
|
ConditionalJudgment: defineAsyncComponent(() =>
|
||||||
import("components/composer/control/ConditionalJudgment.vue")
|
import("components/composer/control/ConditionalJudgment.vue")
|
||||||
),
|
),
|
||||||
|
LoopControl: defineAsyncComponent(() =>
|
||||||
|
import("components/composer/control/LoopControl.vue")
|
||||||
|
),
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
command: {
|
command: {
|
||||||
|
@ -194,45 +194,32 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkAllChainOrders(commands) {
|
checkAllChainOrders(commands) {
|
||||||
// 获取所有不同的 chainId
|
// 按chainId分组
|
||||||
const chainIds = new Set(
|
const chainGroups = commands.reduce((groups, cmd) => {
|
||||||
commands.filter((cmd) => cmd.chainId).map((cmd) => cmd.chainId)
|
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) {
|
return Object.values(chainGroups).every((chainCommands) => {
|
||||||
// 获取当前链的所有命令的索引
|
const commandChain = chainCommands[0].commandChain;
|
||||||
const indices = commands
|
const firstCommand = chainCommands[0];
|
||||||
.map((cmd, index) => ({ cmd, index }))
|
const lastCommand = chainCommands[chainCommands.length - 1];
|
||||||
.filter((item) => item.cmd.chainId === chainId)
|
// 对于每个chain来说,第一个命令必须是chainCommands的第一个命令
|
||||||
.map((item) => item.index);
|
if (firstCommand.commandType !== commandChain[0]) return false;
|
||||||
|
// 最后一个命令必须是chainCommands的最后一个命令
|
||||||
// 获取 if、else、end 的位置
|
if (lastCommand.commandType !== commandChain[commandChain.length - 1])
|
||||||
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 false;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onDrop(event) {
|
onDrop(event) {
|
||||||
try {
|
try {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row items-center">
|
<div class="row items-center">
|
||||||
<!-- 折叠按钮 -->
|
<!-- 折叠按钮 -->
|
||||||
<div class="collapse-btn" v-if="!isLastCommandInChain">
|
<div class="collapse-btn" v-if="!isControlFlow || isFirstCommandInChain">
|
||||||
<q-btn
|
<q-btn
|
||||||
:icon="isCollapsed ? 'expand_more' : 'expand_less'"
|
:icon="isCollapsed ? 'expand_more' : 'expand_less'"
|
||||||
dense
|
dense
|
||||||
@ -12,8 +12,11 @@
|
|||||||
<q-tooltip>折叠/展开此{{ isControlFlow ? "流程" : "命令" }}</q-tooltip>
|
<q-tooltip>折叠/展开此{{ isControlFlow ? "流程" : "命令" }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="end-icon">
|
<div v-else-if="isLastCommandInChain" class="chain-icon">
|
||||||
<q-icon name="last_page" size="xs" />
|
<q-icon name="linear_scale" size="xs" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="chain-icon">
|
||||||
|
<q-icon name="fork_left" size="xs" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
@ -93,15 +96,19 @@ export default {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.collapse-btn,
|
.collapse-btn,
|
||||||
.end-icon {
|
.chain-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chain-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.collapse-btn :deep(.q-btn),
|
.collapse-btn :deep(.q-btn),
|
||||||
.end-icon {
|
.chain-icon {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="conditional-judgment">
|
<div class="conditional-judgment">
|
||||||
<div class="row items-end no-wrap">
|
<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-if="type === 'if'">如果满足</template>
|
||||||
<template v-else-if="type === 'else'">
|
<template v-else-if="type === 'else'">
|
||||||
{{ showCondition ? "否则满足" : "否则" }}
|
{{ showCondition ? "否则满足" : "否则" }}
|
||||||
@ -158,9 +158,8 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.type-label {
|
.control-type-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--q-primary);
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@ -193,11 +192,6 @@ export default defineComponent({
|
|||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.body--dark .type-label {
|
|
||||||
color: var(--q-primary);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body--dark .control-btn {
|
.body--dark .control-btn {
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
237
src/components/composer/control/LoopControl.vue
Normal file
237
src/components/composer/control/LoopControl.vue
Normal 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>
|
@ -10,8 +10,8 @@ const STYLE_CONSTANTS = {
|
|||||||
goldenRatio: 0.618033988749895,
|
goldenRatio: 0.618033988749895,
|
||||||
hueStep: 360 * 0.618033988749895,
|
hueStep: 360 * 0.618033988749895,
|
||||||
indent: 5,
|
indent: 5,
|
||||||
lightSl: "60%, 60%",
|
lightSl: "70%, 60%",
|
||||||
darkSl: "60%, 40%",
|
darkSl: "60%, 60%",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 工具函数
|
// 工具函数
|
||||||
@ -158,10 +158,18 @@ export default defineComponent({
|
|||||||
boxShadow: shadows.light.join(", "),
|
boxShadow: shadows.light.join(", "),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
styleRules["." + className + " .control-type-label"] = {
|
||||||
|
color: `hsl(${hue}, ${lightSl})`,
|
||||||
|
};
|
||||||
|
|
||||||
styleRules[".body--dark ." + className] = {
|
styleRules[".body--dark ." + className] = {
|
||||||
...commonStyle,
|
...commonStyle,
|
||||||
boxShadow: shadows.dark.join(", "),
|
boxShadow: shadows.dark.join(", "),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
styleRules[".body--dark ." + className + " .control-type-label"] = {
|
||||||
|
color: `hsl(${hue}, ${darkSl})`,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. 生成最终的样式字符串
|
// 4. 生成最终的样式字符串
|
||||||
|
@ -9,5 +9,12 @@ export const controlCommands = {
|
|||||||
isControlFlow: true,
|
isControlFlow: true,
|
||||||
commandChain: ["if", "end"],
|
commandChain: ["if", "end"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "loop",
|
||||||
|
label: "循环",
|
||||||
|
component: "LoopControl",
|
||||||
|
isControlFlow: true,
|
||||||
|
commandChain: ["loop", "end"],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user