调整可视化编排布局,更加紧凑,适合utools小窗口

This commit is contained in:
fofolee 2024-12-29 15:11:48 +08:00
parent 4d6b699b7d
commit 7c1ca78ec0
17 changed files with 133 additions and 478 deletions

View File

@ -196,4 +196,75 @@ export default defineComponent({
.body--dark .command-section:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
/* 布局更加紧凑 */
/* 输入框高度及字体 */
.command-composer :deep(.q-field--filled .q-field__control),
.command-composer :deep(.q-field--filled .q-field__control > *),
.command-composer
:deep(.q-field--filled.q-select--with-input .q-field__native) {
border-radius: 5px;
font-size: 12px;
max-height: 36px !important;
min-height: 36px !important;
}
/* 输入框图标大小 */
.command-composer :deep(.q-field--filled .q-field__control .q-icon) {
font-size: 18px;
}
/* 输入框标签字体大小,占位时的位置 */
.command-composer :deep(.q-field--filled .q-field__label) {
font-size: 11px;
top: 8px;
}
/* 输入框标签悬浮的位置 */
.command-composer :deep(.q-field--filled .q-field--float .q_field__label) {
transform: translateY(-35%) scale(0.7);
}
/* 去除filled输入框边框 */
.command-composer :deep(.q-field--filled .q-field__control:before) {
border: none;
}
/* 去除filled输入框下划线 */
.command-composer :deep(.q-field--filled .q-field__control:after) {
height: 0;
border-bottom: none;
}
/* 输入框背景颜色 */
.command-composer :deep(.q-field--filled .q-field__control) {
background: rgba(0, 0, 0, 0.03);
}
/* 输入框聚焦时的背景颜色 */
.command-composer
:deep(.q-field--filled.q-field--highlighted .q-field__control) {
background: rgba(0, 0, 0, 0.03);
}
/* 暗黑模式下的输入框背景颜色 */
.body--dark .command-composer :deep(.q-field--filled .q-field__control) {
background: rgba(255, 255, 255, 0.04);
}
/* 暗黑模式下输入框聚焦时的背景颜色 */
.body--dark
.command-composer
:deep(.q-field--filled.q-field--highlighted .q-field__control) {
background: rgba(255, 255, 255, 0.08);
}
/* checkbox大小及字体 */
.command-composer :deep(.q-checkbox__label) {
font-size: 12px;
}
.command-composer :deep(.q-checkbox__inner) {
font-size: 24px;
}
</style>

View File

@ -13,7 +13,7 @@
<div class="drag-handle cursor-move q-mr-sm">
<q-icon name="drag_indicator" size="18px" class="text-grey-6" />
</div>
<div class="text-subtitle1">{{ command.label }}</div>
<div class="text-subtitle2">{{ command.label }}</div>
<q-space />
<!-- 输出变量设置 -->

View File

@ -26,7 +26,7 @@
command.label
}}</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-section side style="padding-left: 8px">
<q-icon name="drag_indicator" color="grey-6" size="16px" />
</q-item-section>
</q-item>

View File

@ -6,7 +6,6 @@
v-model="mainKey"
:options="commonKeys"
dense
square
filled
use-input
hide-dropdown-icon

View File

@ -1,413 +0,0 @@
<template>
<div
class="logic-flow-card"
@dragover.stop.prevent
@drop.stop.prevent="onCardDrop"
>
<q-card class="logic-container">
<q-card-section class="q-pa-sm">
<!-- 标题栏 -->
<div class="row items-center q-mb-sm">
<div class="drag-handle cursor-move q-mr-sm">
<q-icon name="drag_indicator" size="18px" class="text-grey-6" />
</div>
<div class="text-subtitle1">{{ command.label }}</div>
<q-space />
<q-btn
flat
round
dense
icon="close"
@click="$emit('remove')"
size="sm"
/>
</div>
<!-- 条件输入 -->
<q-input
v-model="condition"
dense
outlined
label="条件表达式"
class="q-mb-md"
>
<template v-slot:prepend>
<q-icon name="code" size="18px" />
</template>
</q-input>
<!-- 分支流程 -->
<div class="branch-flows q-gutter-y-md">
<!-- IF 分支 -->
<div class="branch-container">
<div class="branch-header q-mb-sm">IF 分支</div>
<div
class="branch-drop-area"
:class="{ 'is-active': isIfBranchActive }"
@dragover.stop.prevent="onBranchDragOver('if', $event)"
@drop.stop.prevent="onBranchDrop('if', $event)"
@dragleave.prevent="onBranchDragLeave('if')"
>
<!-- 拖拽指示器 -->
<div v-if="isIfBranchActive" class="branch-indicator">
<q-icon name="add" size="24px" class="text-primary" />
<div class="text-caption text-primary q-mt-xs">
添加到 IF 分支
</div>
</div>
<draggable
v-model="ifCommands"
group="commands"
item-key="id"
handle=".drag-handle"
:animation="200"
>
<template #item="{ element, index }">
<ComposerCard
:command="element"
:placeholder="getPlaceholder(element, index, 'if')"
@remove="removeCommand('if', index)"
@toggle-output="toggleSaveOutput('if', index)"
@update:argv="(val) => handleArgvChange('if', index, val)"
/>
</template>
</draggable>
</div>
</div>
<!-- ELSE 分支 -->
<div class="branch-container">
<div class="branch-header q-mb-sm">ELSE 分支</div>
<div
class="branch-drop-area"
:class="{ 'is-active': isElseBranchActive }"
@dragover.stop.prevent="onBranchDragOver('else', $event)"
@drop.stop.prevent="onBranchDrop('else', $event)"
@dragleave.prevent="onBranchDragLeave('else')"
>
<!-- 拖拽指示器 -->
<div v-if="isElseBranchActive" class="branch-indicator">
<q-icon name="add" size="24px" class="text-primary" />
<div class="text-caption text-primary q-mt-xs">
添加到 ELSE 分支
</div>
</div>
<draggable
v-model="elseCommands"
group="commands"
item-key="id"
handle=".drag-handle"
:animation="200"
>
<template #item="{ element, index }">
<ComposerCard
:command="element"
:placeholder="getPlaceholder(element, index, 'else')"
@remove="removeCommand('else', index)"
@toggle-output="toggleSaveOutput('else', index)"
@update:argv="(val) => handleArgvChange('else', index, val)"
/>
</template>
</draggable>
</div>
</div>
</div>
</q-card-section>
</q-card>
</div>
</template>
<script>
import { defineComponent } from "vue";
import draggable from "vuedraggable";
import ComposerCard from "./ComposerCard.vue";
export default defineComponent({
name: "LogicFlowCard",
components: {
draggable,
ComposerCard,
},
props: {
command: {
type: Object,
required: true,
},
modelValue: {
type: Object,
default: () => ({
condition: "",
if: [],
else: [],
}),
},
},
emits: ["update:modelValue", "remove"],
data() {
return {
isIfBranchActive: false,
isElseBranchActive: false,
};
},
computed: {
condition: {
get() {
return this.modelValue.condition;
},
set(value) {
this.$emit("update:modelValue", {
...this.modelValue,
condition: value,
});
},
},
ifCommands: {
get() {
return this.modelValue.if;
},
set(value) {
this.$emit("update:modelValue", {
...this.modelValue,
if: value,
});
},
},
elseCommands: {
get() {
return this.modelValue.else;
},
set(value) {
this.$emit("update:modelValue", {
...this.modelValue,
else: value,
});
},
},
},
methods: {
//
onBranchDragOver(branch, event) {
//
try {
const types = event.dataTransfer.types;
if (!types.includes("application/json")) {
return;
}
} catch (e) {
return;
}
event.stopPropagation();
if (branch === "if") {
this.isIfBranchActive = true;
} else {
this.isElseBranchActive = true;
}
},
onBranchDragLeave(branch) {
if (branch === "if") {
this.isIfBranchActive = false;
} else {
this.isElseBranchActive = false;
}
},
onBranchDrop(branch, event) {
event.stopPropagation();
let actionData;
try {
const data = event.dataTransfer.getData("application/json");
if (!data) {
console.warn("No valid drag data found");
return;
}
actionData = JSON.parse(data);
} catch (e) {
console.error("Failed to parse drag data:", e);
return;
}
//
if (!actionData || !actionData.value) {
console.warn("Invalid command data");
return;
}
const newCommand = {
...actionData,
id: Date.now(),
argv: "",
saveOutput: false,
useOutput: null,
cmd: actionData.value || actionData.cmd,
value: actionData.value || actionData.cmd,
};
const commands =
branch === "if" ? [...this.ifCommands] : [...this.elseCommands];
commands.push(newCommand);
this.$emit("update:modelValue", {
...this.modelValue,
[branch]: commands,
});
//
if (branch === "if") {
this.isIfBranchActive = false;
} else {
this.isElseBranchActive = false;
}
//
document.querySelectorAll(".dragging").forEach((el) => {
el.classList.remove("dragging");
});
},
// -
onCardDrop(event) {
//
return;
},
//
removeCommand(branch, index) {
const commands =
branch === "if" ? [...this.ifCommands] : [...this.elseCommands];
commands.splice(index, 1);
this.$emit("update:modelValue", {
...this.modelValue,
[branch]: commands,
});
},
toggleSaveOutput(branch, index) {
const commands =
branch === "if" ? [...this.ifCommands] : [...this.elseCommands];
commands[index].saveOutput = !commands[index].saveOutput;
// 使
if (!commands[index].saveOutput) {
commands.forEach((cmd, i) => {
if (i > index && cmd.useOutput === index) {
cmd.useOutput = null;
}
});
}
this.$emit("update:modelValue", {
...this.modelValue,
[branch]: commands,
});
},
handleArgvChange(branch, index, value) {
const commands =
branch === "if" ? [...this.ifCommands] : [...this.elseCommands];
commands[index].argv = value;
this.$emit("update:modelValue", {
...this.modelValue,
[branch]: commands,
});
},
getPlaceholder(element, index, branch) {
if (element.useOutput !== null) {
const commands = branch === "if" ? this.ifCommands : this.elseCommands;
return `使用 ${commands[element.useOutput].label} 的输出`;
}
return element.desc;
},
},
});
</script>
<style scoped>
.logic-flow-card {
margin-bottom: 8px;
}
.logic-container {
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.branch-container {
background: rgba(0, 0, 0, 0.02);
border-radius: 4px;
padding: 12px;
}
.branch-header {
font-weight: 500;
color: var(--q-primary);
font-size: 14px;
}
.branch-drop-area {
min-height: 50px;
border: 1px dashed rgba(0, 0, 0, 0.1);
border-radius: 4px;
transition: all 0.3s ease;
padding: 4px;
position: relative;
}
.branch-drop-area.is-active {
border-color: var(--q-primary);
background: rgba(var(--q-primary-rgb), 0.03);
}
/* 拖拽指示器样式 */
.branch-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
z-index: 1;
background: rgba(255, 255, 255, 0.9);
padding: 12px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
animation: indicator-fade-in 0.3s ease;
}
@keyframes indicator-fade-in {
from {
opacity: 0;
transform: translate(-50%, -40%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
.body--dark .logic-container {
background: rgba(34, 34, 34, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.body--dark .branch-container {
background: rgba(255, 255, 255, 0.03);
}
.body--dark .branch-drop-area {
border-color: rgba(255, 255, 255, 0.1);
}
.body--dark .branch-indicator {
background: rgba(0, 0, 0, 0.7);
}
</style>

View File

@ -3,7 +3,7 @@
v-if="!isNumber"
v-model="localValue"
dense
outlined
filled
:label="label"
class="variable-input"
>
@ -88,7 +88,7 @@
type="number"
v-model.number="localValue"
dense
outlined
filled
:label="label"
class="number-input"
>
@ -346,22 +346,24 @@ export default defineComponent({
}
.number-controls {
border-left: 1px solid rgba(0, 0, 0, 0.12);
/* margin-left: 4px; */
padding-left: 2px;
}
.body--dark .number-controls {
border-left-color: rgba(255, 255, 255, 0.12);
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.number-btn {
opacity: 0.7;
font-size: 18px;
padding: 2px;
font-size: 12px;
padding: 0;
margin: 0;
min-height: 20px;
/* min-width: 24px; */
min-height: 16px;
height: 16px;
width: 20px;
}
.number-btn :deep(.q-icon) {
font-size: 12px;
}
.number-btn:hover {

View File

@ -17,7 +17,7 @@
]"
label="请求方法"
dense
outlined
filled
emit-value
map-options
@update:model-value="updateConfig"
@ -108,7 +108,7 @@
:options="['json', 'text', 'blob', 'arraybuffer']"
label="响应类型"
dense
outlined
filled
emit-value
map-options
@update:model-value="updateConfig"

View File

@ -16,7 +16,7 @@
:options="['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']"
label="请求方法"
dense
outlined
filled
emit-value
map-options
@update:model-value="updateConfig"
@ -57,7 +57,7 @@
:options="['follow', 'error', 'manual']"
label="重定向策略"
dense
outlined
filled
emit-value
map-options
@update:model-value="updateConfig"

View File

@ -7,7 +7,7 @@
:options="commonHeaders"
label="添加常用Header"
dense
outlined
filled
emit-value
map-options
style="width: 200px"
@ -33,7 +33,7 @@
v-model="header.name"
label="Header名称"
dense
outlined
filled
@update:model-value="emitUpdate"
/>
<q-input
@ -41,7 +41,7 @@
:model-value="header.name"
label="Header名称"
dense
outlined
filled
readonly
/>
</div>

View File

@ -37,7 +37,7 @@
:options="userAgentOptions"
label="常用 UA"
dense
outlined
filled
emit-value
map-options
options-dense

View File

@ -131,3 +131,13 @@ export default defineComponent({
},
});
</script>
<style scoped>
.ubrowser-editor :deep(.q-stepper) {
padding: 0 !important;
}
.ubrowser-editor :deep(.q-stepper__tab) {
padding: 5px 25px;
}
</style>

View File

@ -193,14 +193,14 @@ export default defineComponent({
padding: 2px 4px;
border-color: rgba(0, 0, 0, 0.15);
}
/*
.operation-item:hover {
background: rgba(0, 0, 0, 0.05);
}
.body--dark .operation-item:hover {
background: rgba(0, 0, 0, 0.25);
}
} */
.move-btn {
opacity: 0.6;

View File

@ -23,6 +23,11 @@
label="全屏显示"
@update:model-value="updateConfig('fullscreen', $event)"
/>
<q-checkbox
:model-value="localConfigs.run.fullscreenable"
label="允许全屏"
@update:model-value="updateConfig('fullscreenable', $event)"
/>
</div>
</div>
@ -33,8 +38,6 @@
<VariableInput
v-model="localConfigs.run.width"
label="窗口宽度"
dense
outlined
:command="{ inputType: 'number' }"
@update:model-value="updateConfig('width', $event)"
/>
@ -43,8 +46,6 @@
<VariableInput
v-model="localConfigs.run.height"
label="窗口高度"
dense
outlined
:command="{ inputType: 'number' }"
@update:model-value="updateConfig('height', $event)"
/>
@ -53,8 +54,6 @@
<VariableInput
v-model="localConfigs.run.x"
label="X坐标"
dense
outlined
:command="{ inputType: 'number' }"
@update:model-value="updateConfig('x', $event)"
/>
@ -63,8 +62,6 @@
<VariableInput
v-model="localConfigs.run.y"
label="Y坐标"
dense
outlined
:command="{ inputType: 'number' }"
@update:model-value="updateConfig('y', $event)"
/>
@ -79,8 +76,6 @@
<VariableInput
v-model="localConfigs.run.minWidth"
label="最小宽度"
dense
outlined
:command="{ inputType: 'number' }"
@update:model-value="updateConfig('minWidth', $event)"
/>
@ -89,8 +84,6 @@
<VariableInput
v-model="localConfigs.run.minHeight"
label="最小高度"
dense
outlined
:command="{ inputType: 'number' }"
@update:model-value="updateConfig('minHeight', $event)"
/>
@ -99,8 +92,6 @@
<VariableInput
v-model="localConfigs.run.maxWidth"
label="最大宽度"
dense
outlined
:command="{ inputType: 'number' }"
@update:model-value="updateConfig('maxWidth', $event)"
/>
@ -109,8 +100,6 @@
<VariableInput
v-model="localConfigs.run.maxHeight"
label="最大高度"
dense
outlined
:command="{ inputType: 'number' }"
@update:model-value="updateConfig('maxHeight', $event)"
/>
@ -141,29 +130,18 @@
label="可最大化"
@update:model-value="updateConfig('maximizable', $event)"
/>
</div>
</div>
<!-- 特殊功能控制 -->
<div class="col-12">
<div class="row items-center q-gutter-x-md">
<q-checkbox
:model-value="localConfigs.run.enableLargerThanScreen"
label="允许超出屏幕"
@update:model-value="updateConfig('enableLargerThanScreen', $event)"
/>
<q-checkbox
:model-value="localConfigs.run.fullscreenable"
label="允许全屏"
@update:model-value="updateConfig('fullscreenable', $event)"
/>
</div>
</div>
<!-- 透明度控制 -->
<div class="col-12">
<div class="row items-center">
<div class="q-mr-md">透明度</div>
<div class="row items-center" style="height: 36px">
<div class="q-mr-md" style="font-size: 12px">透明度</div>
<q-slider
class="col"
v-model="localConfigs.run.opacity"
@ -171,8 +149,9 @@
:max="1"
:step="0.1"
label
label-always
color="primary"
switch-label-side
dense
@update:model-value="updateConfig('opacity', $event)"
>
<template v-slot:thumb-label>

View File

@ -14,7 +14,7 @@
:options="deviceOptions"
label="常用设备"
dense
outlined
filled
emit-value
map-options
options-dense

View File

@ -9,6 +9,7 @@
use-chips
multiple
dense
borderless
hide-dropdown-icon
options-dense
input-debounce="0"
@ -20,10 +21,10 @@
ref="paramSelect"
>
<template v-slot:prepend>
<div class="text-primary text-bold">(</div>
<div class="text-primary func-symbol">(</div>
</template>
<template v-slot:append>
<div class="text-primary text-bold">)</div>
<div class="text-primary func-symbol">)</div>
</template>
</q-select>
</div>
@ -33,15 +34,15 @@
:label="label"
type="textarea"
dense
outlined
borderless
autogrow
@update:model-value="updateFunction"
>
<template v-slot:prepend>
<div class="text-primary text-bold">=> {</div>
<div class="text-primary func-symbol">=> {</div>
</template>
<template v-slot:append>
<div class="text-primary text-bold">}</div>
<div class="text-primary func-symbol">}</div>
</template>
</q-input>
</div>
@ -66,7 +67,7 @@
v-model="paramValues[param]"
:label="`传递给参数 ${param} 的值`"
dense
outlined
filled
@update:model-value="updateParamValue(param, $event)"
/>
</div>
@ -195,3 +196,9 @@ export default defineComponent({
},
});
</script>
<style scoped>
:deep(.q-field__control) .text-primary.func-symbol {
font-size: 18px !important;
}
</style>

View File

@ -4,7 +4,7 @@
:label="label"
:options="options"
dense
outlined
filled
emit-value
map-options
@update:model-value="$emit('update:modelValue', $event)"

View File

@ -4,7 +4,7 @@
:label="label"
type="textarea"
dense
outlined
filled
autogrow
@update:model-value="$emit('update:modelValue', $event)"
>