调整子流程标签栏样式,支持重命名

This commit is contained in:
fofolee 2025-01-21 22:26:37 +08:00
parent e0208eb119
commit db0ce31c7d
6 changed files with 180 additions and 128 deletions

View File

@ -3,7 +3,7 @@
<!-- 主体内容 --> <!-- 主体内容 -->
<div class="composer-body row no-wrap"> <div class="composer-body row no-wrap">
<!-- 左侧命令列表 --> <!-- 左侧命令列表 -->
<div class="col-3 command-section"> <div class="command-section" style="width: 180px">
<ComposerList :commands="availableCommands" @add-command="addCommand" /> <ComposerList :commands="availableCommands" @add-command="addCommand" />
</div> </div>
@ -134,7 +134,7 @@ export default defineComponent({
} }
.body--dark .command-section { .body--dark .command-section {
background: #1d1d1d; background: #232323;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
} }

View File

@ -230,7 +230,7 @@ export default defineComponent({
} }
.body--dark .composer-list { .body--dark .composer-list {
background-color: rgba(32, 32, 32, 0.8); background-color: #232323;
} }
.category-item { .category-item {

View File

@ -1,70 +1,71 @@
<template> <template>
<div class="flow-tabs"> <div class="flow-tabs">
<div class="tabs-header"> <div class="tabs-header">
<div class="header-content"> <div class="tabs-container">
<div class="tabs-container"> <!-- main 作为固定按钮 -->
<!-- main 作为固定按钮 --> <q-btn
<q-btn flat
flat dense
dense label="主流程"
:color="activeTab === 'main' ? 'primary' : 'grey'" class="main-btn"
label="main" :class="{ 'main-btn-active': activeTab === 'main' }"
class="main-btn" @click="activeTab = 'main'"
@click="activeTab = 'main'" />
/>
<!-- 其他流程标签可滚动 --> <!-- 其他流程标签可滚动 -->
<q-tabs <q-tabs
v-model="activeTab" v-model="activeTab"
dense dense
class="text-grey" class="text-grey"
active-color="primary" active-color="primary"
indicator-color="primary" indicator-color="primary"
align="left" align="left"
narrow-indicator narrow-indicator
> outside-arrows
<template v-for="flow in nonMainFlows" :key="flow.id"> >
<q-tab :name="flow.id" class="flow-tab"> <template v-for="flow in nonMainFlows" :key="flow.id">
<div class="flow-tab-content"> <q-tab :name="flow.id" class="flow-tab">
<div class="flow-tab-content">
<template v-if="flow.isEditing">
<q-input <q-input
v-model="flow.name" v-model="flow.label"
dense dense
borderless borderless
class="flow-name-input" class="flow-name-input"
@keydown.space.prevent @keydown.space.prevent
@blur="validateFlowName(flow)" @blur="finishEdit(flow)"
@keyup.enter="finishEdit(flow)"
ref="inputRefs"
/> />
<q-btn </template>
flat <template v-else>
dense <span class="flow-name-text" @dblclick="startEdit(flow)">{{
round flow.label
icon="close" }}</span>
size="xs" </template>
@click.stop="removeFlow(flow)" <q-btn
/> flat
</div> dense
</q-tab> round
</template> icon="close"
</q-tabs> size="xs"
@click.stop="removeFlow(flow)"
/>
</div>
<q-tooltip> 双击修改名称 </q-tooltip>
</q-tab>
</template>
</q-tabs>
<q-btn <q-icon dense name="add" class="add-btn" @click="addFlow" />
flat
dense
round
icon="add"
size="sm"
class="q-ml-sm add-btn"
@click="addFlow"
/>
</div>
<ComposerButtons
:generate-code="generateAllFlowCode"
:is-all-collapsed="isAllCollapsed"
:show-close-button="showCloseButton"
@action="handleAction"
/>
</div> </div>
<ComposerButtons
:generate-code="generateAllFlowCode"
:is-all-collapsed="isAllCollapsed"
:show-close-button="showCloseButton"
@action="handleAction"
/>
</div> </div>
<div class="flow-container"> <div class="flow-container">
@ -88,6 +89,7 @@ import ComposerFlow from "./ComposerFlow.vue";
import ComposerButtons from "./flow/ComposerButtons.vue"; import ComposerButtons from "./flow/ComposerButtons.vue";
import { generateCode } from "js/composer/generateCode"; import { generateCode } from "js/composer/generateCode";
import { findCommandByValue } from "js/composer/composerConfig"; import { findCommandByValue } from "js/composer/composerConfig";
import { generateUniqSuffix } from "js/composer/variableManager";
export default defineComponent({ export default defineComponent({
name: "FlowTabs", name: "FlowTabs",
@ -107,6 +109,7 @@ export default defineComponent({
{ {
id: "main", id: "main",
name: "main", name: "main",
label: "主流程",
commands: [], commands: [],
}, },
], ],
@ -120,44 +123,43 @@ export default defineComponent({
}, },
}, },
methods: { methods: {
generateFlowName(baseName = "flow_") {
return (
baseName +
generateUniqSuffix(
baseName,
this.flows.map((f) => f.name),
false
)
);
},
addFlow() { addFlow() {
const id = `flow_${this.$root.getUniqueId()}`; const id = this.$root.getUniqueId();
const name = `flow${this.flows.length}`; const name = this.generateFlowName();
this.flows.push({ this.flows.push({
id, id,
name, name,
label: name.replace("flow_", "子流程"),
commands: [], commands: [],
}); });
this.activeTab = id; this.activeTab = id;
this.$nextTick(this.updateWidths);
}, },
removeFlow(flow) { removeFlow(flow) {
const index = this.flows.findIndex((f) => f.id === flow.id); const index = this.flows.findIndex((f) => f.id === flow.id);
if (index > -1 && flow.id !== "main") { if (index > -1 && flow.id !== "main") {
this.flows.splice(index, 1); this.flows.splice(index, 1);
this.activeTab = this.flows[0].id; this.activeTab = this.flows[0].id;
this.$nextTick(this.updateWidths);
} }
}, },
validateFlowName(flow) {
if (flow.id === "main") return;
//
let newName = flow.name.replace(/\s+/g, "_");
let counter = 1;
const baseName = newName;
while (this.flows.some((f) => f.id !== flow.id && f.name === newName)) {
newName = `${baseName}_${counter++}`;
}
flow.name = newName;
},
generateFlowCode(flow) { generateFlowCode(flow) {
return generateCode(flow.commands, flow.name); return generateCode(flow);
}, },
generateAllFlowCode() { generateAllFlowCode() {
// flow // flow
return this.flows.map((flow) => this.generateFlowCode(flow)).join("\n\n"); return this.flows
.reverse()
.map((flow) => this.generateFlowCode(flow))
.join("\n\n");
}, },
handleFlowAction(type, payload, flow) { handleFlowAction(type, payload, flow) {
if (type === "close") { if (type === "close") {
@ -255,6 +257,21 @@ export default defineComponent({
}); });
this.isAllCollapsed = false; this.isAllCollapsed = false;
}, },
startEdit(flow) {
flow.isEditing = true;
this.$nextTick(() => {
const input = this.$refs.inputRefs?.[0];
if (input) {
input.focus();
}
});
},
finishEdit(flow) {
flow.isEditing = false;
if (!flow.label) {
flow.label = this.generateFlowName().replace("flow_", "子流程");
}
},
}, },
}); });
</script> </script>
@ -268,15 +285,11 @@ export default defineComponent({
.tabs-header { .tabs-header {
flex-shrink: 0; flex-shrink: 0;
height: 28px; height: 30px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.header-content {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
height: 100%; border-bottom: 1px solid rgba(0, 0, 0, 0.05);
} }
.tabs-container { .tabs-container {
@ -285,63 +298,94 @@ export default defineComponent({
align-items: center; align-items: center;
height: 100%; height: 100%;
min-width: 0; min-width: 0;
margin-right: 8px; width: 0;
} }
/* 限制在当前组件内的tabs样式 */ /* 限制在当前组件内的tabs样式 */
.tabs-container :deep(.q-tabs) { .tabs-container :deep(.q-tabs) {
flex: 1; height: 30px;
height: 28px; min-height: 30px;
min-height: 28px; min-width: 0;
overflow-x: auto; width: auto;
overflow-y: hidden; max-width: 100%;
margin-left: 4px; /* 与 main 按钮保持间距 */
} }
/* 隐藏滚动条 */ .body--dark .tabs-container :deep(.q-tabs) {
.tabs-container :deep(.q-tabs)::-webkit-scrollbar { background-color: #232323 !important;
display: none;
} }
.tabs-container :deep(.q-tab) { .tabs-container :deep(.q-tab) {
min-height: 28px; min-height: 30px;
height: 28px; height: 30px;
padding: 0 8px; padding: 0;
text-align: center;
background-color: transparent;
}
.tabs-container
:deep(.q-tabs--scrollable.q-tabs__arrows--outside.q-tabs--horizontal) {
padding: 0 15px;
}
.tabs-container :deep(.q-tabs__arrow) {
min-width: 0;
width: 15px;
font-size: 15px;
} }
.tabs-container :deep(.q-tab__content) { .tabs-container :deep(.q-tab__content) {
min-width: 0; min-width: 0;
width: 100%;
} }
.flow-tab { .flow-tab {
min-width: 80px; min-width: 80px;
max-width: 120px;
} }
.flow-tab-content { .flow-tab-content {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; user-select: none;
width: 100%;
} }
.flow-name-input { .flow-name-input {
max-width: 100px; max-width: 100%;
min-width: 0;
} }
.flow-name-input :deep(.q-field__native) { .flow-name-input :deep(.q-field__control),
padding: 0; .flow-name-input :deep(.q-field__control *) {
font-size: 12px; font-size: 12px;
height: 30px;
text-align: center;
}
.flow-name-text {
font-size: 12px;
padding: 0 2px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
min-width: 0;
} }
/* 添加按钮样式 */ /* 添加按钮样式 */
.tabs-container .q-btn {
height: 28px;
min-height: 28px;
}
.add-btn { .add-btn {
flex-shrink: 0; flex-shrink: 0;
height: 28px; padding: 0 5px;
min-height: 28px; font-size: 16px;
cursor: pointer;
transition: all 0.2s ease-in-out;
}
.add-btn:hover {
color: var(--q-primary);
transform: scale(1.2);
transition: all 0.2s ease-in-out;
} }
.flow-container { .flow-container {
@ -355,13 +399,19 @@ export default defineComponent({
} }
.main-btn { .main-btn {
height: 28px; height: 30px;
min-height: 28px; min-height: 30px;
padding: 0 12px; padding: 0 12px;
border-radius: 4px; border-radius: 8px 0 0 0;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
margin-right: 4px;
flex-shrink: 0; flex-shrink: 0;
color: var(--q-text-color);
}
.main-btn-active {
color: var(--q-primary);
border-bottom: 2px solid var(--q-primary);
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
} }
</style> </style>

View File

@ -1,12 +1,11 @@
<template> <template>
<div class="composer-buttons"> <div class="composer-buttons">
<div class="right-buttons"> <div class="right-buttons">
<q-separator vertical />
<q-btn <q-btn
:icon="isAllCollapsed ? 'unfold_more' : 'unfold_less'" :icon="isAllCollapsed ? 'unfold_more' : 'unfold_less'"
dense dense
flat flat
rounded
size="9px"
@click="$emit('action', isAllCollapsed ? 'expandAll' : 'collapseAll')" @click="$emit('action', isAllCollapsed ? 'expandAll' : 'collapseAll')"
> >
<q-tooltip>{{ isAllCollapsed ? "展开所有" : "折叠所有" }}</q-tooltip> <q-tooltip>{{ isAllCollapsed ? "展开所有" : "折叠所有" }}</q-tooltip>

View File

@ -1,16 +1,18 @@
export function generateCode(commandFlow, functionName = null) { export function generateCode(flow) {
const { commands, name, label } = flow;
// 检查是否包含异步函数 // 检查是否包含异步函数
const hasAsyncFunction = commandFlow.some((cmd) => cmd.isAsync); const hasAsyncFunction = commands.some((cmd) => cmd.isAsync);
let code = []; let code = [];
const funcName = functionName || "run"; const funcName = name || "func_" + new Date().getTime();
code.push(`// ${label}`);
// 生成函数声明 // 生成函数声明
code.push(`${hasAsyncFunction ? "async " : ""}function ${funcName}() {`); code.push(`${hasAsyncFunction ? "async " : ""}function ${funcName}() {`);
const indent = " "; const indent = " ";
commandFlow.forEach((cmd) => { commands.forEach((cmd) => {
// 跳过禁用的命令 // 跳过禁用的命令
if (cmd.disabled) return; if (cmd.disabled) return;
if (!cmd.code) return; if (!cmd.code) return;
@ -28,7 +30,7 @@ export function generateCode(commandFlow, functionName = null) {
code.push("}"); // Close the function code.push("}"); // Close the function
// 如果是主函数,则自动执行 // 如果是主函数,则自动执行
if (functionName === "main") { if (funcName === "main") {
code.push("\nmain();"); // Call the main function code.push("\nmain();"); // Call the main function
} }

View File

@ -5,7 +5,7 @@ import { validateVariableName } from "js/common/variableValidator";
* @param {number} varCount - 当前变量数量 * @param {number} varCount - 当前变量数量
* @returns {string} 随机后缀 * @returns {string} 随机后缀
*/ */
function generateRandomSuffix(varCount) { function generateRandomSuffix(varCount, withPrefix = true) {
// 根据变量数量决定后缀长度 // 根据变量数量决定后缀长度
let length = 1; // 默认1位 let length = 1; // 默认1位
if (varCount >= 100) { if (varCount >= 100) {
@ -15,7 +15,7 @@ function generateRandomSuffix(varCount) {
} }
return ( return (
"_" + (withPrefix ? "_" : "") +
Math.random() Math.random()
.toString(36) .toString(36)
.substring(2, 2 + length) .substring(2, 2 + length)
@ -66,18 +66,19 @@ function generateValidVarName(baseName, existingVars, currentName = null) {
} }
// 如果变量名重复,添加随机后缀直到不重复 // 如果变量名重复,添加随机后缀直到不重复
let finalName = baseName; let finalName = baseName + generateUniqSuffix(baseName, existingVars);
while (existingVars.includes(finalName)) {
let suffix;
do {
suffix = generateRandomSuffix(existingVars.length);
} while (existingVars.includes(baseName + suffix));
finalName = baseName + suffix;
}
return finalName; return finalName;
} }
export function generateUniqSuffix(baseName, existingVars, withPrefix = true) {
let suffix;
do {
suffix = generateRandomSuffix(existingVars.length, withPrefix);
} while (existingVars.includes(baseName + suffix));
return suffix;
}
/** /**
* 处理变量更新 * 处理变量更新
* @param {Object} params - 参数对象 * @param {Object} params - 参数对象