mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-08-16 10:39:32 +08:00
优化可编排界面的样式
This commit is contained in:
parent
2c8db1f473
commit
29ceb3c7ff
@ -169,16 +169,12 @@
|
|||||||
<KeyRecorder @sendKeys="addAction" />
|
<KeyRecorder @sendKeys="addAction" />
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<q-dialog v-model="showComposer" maximized>
|
<q-dialog v-model="showComposer" maximized>
|
||||||
<q-card class="full-height">
|
<CommandComposer
|
||||||
<q-card-section class="q-pa-md full-height">
|
ref="composer"
|
||||||
<CommandComposer
|
@run="handleComposerRun"
|
||||||
ref="composer"
|
@apply="handleComposerApply"
|
||||||
@run="handleComposerRun"
|
@update:model-value="showComposer = false"
|
||||||
@apply="handleComposerApply"
|
/>
|
||||||
@update:model-value="showComposer = false"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -298,19 +294,19 @@ export default {
|
|||||||
window.showUb.docs();
|
window.showUb.docs();
|
||||||
},
|
},
|
||||||
handleComposerRun(code) {
|
handleComposerRun(code) {
|
||||||
this.$emit('add-action', code);
|
this.$emit("add-action", code);
|
||||||
},
|
},
|
||||||
handleComposerApply(code) {
|
handleComposerApply(code) {
|
||||||
this.$emit('add-action', code);
|
this.$emit("add-action", code);
|
||||||
this.showComposer = false;
|
this.showComposer = false;
|
||||||
},
|
},
|
||||||
applyComposerCommands() {
|
applyComposerCommands() {
|
||||||
if (this.$refs.composer) {
|
if (this.$refs.composer) {
|
||||||
const code = this.$refs.composer.generateCode();
|
const code = this.$refs.composer.generateCode();
|
||||||
this.$emit('add-action', code);
|
this.$emit("add-action", code);
|
||||||
}
|
}
|
||||||
this.showComposer = false;
|
this.showComposer = false;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="command-composer">
|
<div class="command-composer">
|
||||||
<!-- 固定头部 -->
|
|
||||||
<div class="composer-header q-pa-sm row items-center bg-white">
|
|
||||||
<div class="text-h6 text-weight-medium">可视化命令编排</div>
|
|
||||||
<q-space />
|
|
||||||
<q-btn flat round dense icon="close" v-close-popup />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 主体内容 -->
|
<!-- 主体内容 -->
|
||||||
<div class="composer-body row no-wrap q-pa-sm">
|
<div class="composer-body row no-wrap q-pa-sm">
|
||||||
<!-- 左侧命令列表 -->
|
<!-- 左侧命令列表 -->
|
||||||
<div class="col-3">
|
<div class="col-3 command-section">
|
||||||
<div class="text-subtitle1 q-pb-sm">可用命令</div>
|
<div class="text-subtitle1 q-pb-sm">可用命令</div>
|
||||||
<q-scroll-area style="height: calc(100vh - 200px)">
|
<q-scroll-area class="command-scroll">
|
||||||
<ComposerList
|
<ComposerList
|
||||||
:commands="availableCommands"
|
:commands="availableCommands"
|
||||||
@add-command="addCommand"
|
@add-command="addCommand"
|
||||||
@ -21,16 +14,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧命令流程 -->
|
<!-- 右侧命令流程 -->
|
||||||
<div class="col q-pl-md">
|
<div class="col q-pl-md command-section">
|
||||||
<div class="text-subtitle1 q-pb-sm">命令流程</div>
|
<div class="text-subtitle1 q-pb-sm">命令流程</div>
|
||||||
<q-scroll-area style="height: calc(100vh - 200px)">
|
<q-scroll-area class="command-scroll">
|
||||||
<ComposerFlow v-model="commandFlow" @add-command="addCommand" />
|
<ComposerFlow v-model="commandFlow" @add-command="addCommand" />
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 固定底部 -->
|
<!-- 固定底部 -->
|
||||||
<div class="composer-footer q-pa-sm row items-center justify-end bg-white">
|
<div class="composer-footer q-pa-sm row items-center justify-end">
|
||||||
<q-btn
|
<q-btn
|
||||||
outline
|
outline
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -130,22 +123,37 @@ export default defineComponent({
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background-color: #f4f4f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer-header {
|
.body--dark .command-composer {
|
||||||
border-bottom: 1px solid #e0e0e0;
|
background-color: #303132;
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer-body {
|
.composer-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #f5f5f5;
|
}
|
||||||
|
|
||||||
|
.command-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-scroll {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer-footer {
|
.composer-footer {
|
||||||
border-top: 1px solid #e0e0e0;
|
border-top: 1px solid #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body--dark .composer-footer {
|
||||||
|
border-top: 1px solid #676666;
|
||||||
|
}
|
||||||
|
|
||||||
/* 滚动美化 */
|
/* 滚动美化 */
|
||||||
:deep(.q-scrollarea__thumb) {
|
:deep(.q-scrollarea__thumb) {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
@ -156,4 +164,8 @@ export default defineComponent({
|
|||||||
:deep(.q-scrollarea__thumb:hover) {
|
:deep(.q-scrollarea__thumb:hover) {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.q-scrollarea__content) {
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="composer-card q-mb-sm">
|
<div
|
||||||
<q-card flat bordered>
|
class="composer-card q-pa-xs"
|
||||||
<q-card-section horizontal class="q-pa-sm">
|
:class="{ 'can-drop': canDrop }"
|
||||||
<!-- 拖拽手柄 -->
|
v-bind="$attrs"
|
||||||
<div class="drag-handle cursor-move q-mr-sm">
|
>
|
||||||
<q-icon name="drag_indicator" size="24px" class="text-grey-6" />
|
<q-card class="command-item">
|
||||||
</div>
|
<q-card-section class="q-pa-sm">
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<!-- 命令标题和描述 -->
|
<!-- 命令标题和描述 -->
|
||||||
<div class="row items-center q-mb-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>
|
<div class="text-subtitle1">{{ command.label }}</div>
|
||||||
<q-space />
|
<q-space />
|
||||||
<!-- 输出开关 -->
|
<!-- 输出开关 -->
|
||||||
@ -25,6 +28,7 @@
|
|||||||
dense
|
dense
|
||||||
icon="close"
|
icon="close"
|
||||||
@click="$emit('remove')"
|
@click="$emit('remove')"
|
||||||
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -49,7 +53,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-slot:selected-item="scope">
|
<template v-slot:selected-item="scope">
|
||||||
<div class="row items-center">
|
<div class="row items-center">
|
||||||
<q-icon name="output" color="primary" size="xs" class="q-mr-xs" />
|
<q-icon
|
||||||
|
name="output"
|
||||||
|
color="primary"
|
||||||
|
size="xs"
|
||||||
|
class="q-mr-xs"
|
||||||
|
/>
|
||||||
{{ scope.opt.label }}
|
{{ scope.opt.label }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -69,7 +78,7 @@
|
|||||||
:label="placeholder"
|
:label="placeholder"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="code" />
|
<q-icon name="text_fields" size="18px" />
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
@ -81,87 +90,167 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from "vue";
|
||||||
import KeyEditor from './KeyEditor.vue'
|
import KeyEditor from "./KeyEditor.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ComposerCard',
|
name: "ComposerCard",
|
||||||
components: {
|
components: {
|
||||||
KeyEditor
|
KeyEditor,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
command: {
|
command: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
hasOutput: {
|
hasOutput: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
canUseOutput: {
|
canUseOutput: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
availableOutputs: {
|
availableOutputs: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: "",
|
||||||
}
|
},
|
||||||
|
canDrop: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showKeyRecorder: false
|
showKeyRecorder: false,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
emits: ['remove', 'toggle-output', 'update:argv', 'update:use-output'],
|
emits: ["remove", "toggle-output", "update:argv", "update:use-output"],
|
||||||
computed: {
|
computed: {
|
||||||
saveOutputLocal: {
|
saveOutputLocal: {
|
||||||
get() {
|
get() {
|
||||||
return this.command.saveOutput
|
return this.command.saveOutput;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit('toggle-output')
|
this.$emit("toggle-output");
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
argvLocal: {
|
argvLocal: {
|
||||||
get() {
|
get() {
|
||||||
return this.command.argv
|
return this.command.argv;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit('update:argv', value)
|
this.$emit("update:argv", value);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
useOutputLocal: {
|
useOutputLocal: {
|
||||||
get() {
|
get() {
|
||||||
return this.command.useOutput
|
return this.command.useOutput;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit('update:use-output', value)
|
this.$emit("update:use-output", value);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClearOutput() {
|
handleClearOutput() {
|
||||||
this.$emit('update:use-output', null)
|
this.$emit("update:use-output", null);
|
||||||
},
|
},
|
||||||
handleKeyRecord(keys) {
|
handleKeyRecord(keys) {
|
||||||
this.showKeyRecorder = false;
|
this.showKeyRecorder = false;
|
||||||
// 从keyTap("a","control")格式中提取参数
|
// 从keyTap("a","control")格式中提取参数
|
||||||
const matches = keys.match(/keyTap\((.*)\)/)
|
const matches = keys.match(/keyTap\((.*)\)/);
|
||||||
if (matches && matches[1]) {
|
if (matches && matches[1]) {
|
||||||
this.$emit('update:argv', matches[1]);
|
this.$emit("update:argv", matches[1]);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
mounted() {
|
||||||
|
this.$el.classList.add("composer-card-enter-from");
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.$el.classList.remove("composer-card-enter-from");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.composer-card {
|
.composer-card {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transform-origin: center;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进入动画 */
|
||||||
|
.composer-card-enter-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除动画 */
|
||||||
|
.composer-card-leave-active {
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拖拽动画 */
|
||||||
|
.composer-card:active {
|
||||||
|
transform: scale(1.02);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-item {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-item:hover {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-card :deep(.q-field__label) {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可放置状态动画 */
|
||||||
|
.can-drop {
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.can-drop .command-item {
|
||||||
|
border: 2px dashed var(--q-primary);
|
||||||
|
background: rgba(var(--q-primary-rgb), 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色模式适配 */
|
||||||
|
.body--dark .command-item {
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .command-item:hover {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .can-drop {
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-handle {
|
.drag-handle {
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
<div class="composer-flow">
|
<div class="composer-flow">
|
||||||
<div
|
<div
|
||||||
class="command-flow-container"
|
class="command-flow-container"
|
||||||
@dragover.prevent
|
@dragover.prevent="onDragOver"
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
|
@dragleave.prevent="onDragLeave"
|
||||||
>
|
>
|
||||||
<draggable
|
<draggable
|
||||||
v-model="commands"
|
v-model="commands"
|
||||||
@ -12,14 +13,19 @@
|
|||||||
class="flow-list"
|
class="flow-list"
|
||||||
handle=".drag-handle"
|
handle=".drag-handle"
|
||||||
:animation="200"
|
:animation="200"
|
||||||
|
@start="onDragStart"
|
||||||
|
@end="onDragEnd"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<transition
|
<transition name="slide-fade" mode="out-in" appear>
|
||||||
name="slide-fade"
|
<div
|
||||||
mode="out-in"
|
:key="element.id"
|
||||||
appear
|
class="flow-item"
|
||||||
>
|
:class="{
|
||||||
<div :key="element.id" class="flow-item">
|
'insert-before': dragIndex === index,
|
||||||
|
'insert-after': dragIndex === commands.length && index === commands.length - 1
|
||||||
|
}"
|
||||||
|
>
|
||||||
<ComposerCard
|
<ComposerCard
|
||||||
:command="element"
|
:command="element"
|
||||||
:has-output="hasOutput(element)"
|
:has-output="hasOutput(element)"
|
||||||
@ -41,57 +47,132 @@
|
|||||||
<div class="text-body2 q-mt-sm">从左侧拖拽命令到这里开始编排</div>
|
<div class="text-body2 q-mt-sm">从左侧拖拽命令到这里开始编排</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="drop-area"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from "vue";
|
||||||
import draggable from 'vuedraggable'
|
import draggable from "vuedraggable";
|
||||||
import ComposerCard from './ComposerCard.vue'
|
import ComposerCard from "./ComposerCard.vue";
|
||||||
import { commandsWithOutput, commandsAcceptOutput } from './composerConfig'
|
import { commandsWithOutput, commandsAcceptOutput } from "./composerConfig";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ComposerFlow',
|
name: "ComposerFlow",
|
||||||
components: {
|
components: {
|
||||||
draggable,
|
draggable,
|
||||||
ComposerCard
|
ComposerCard,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'add-command'],
|
emits: ["update:modelValue", "add-command"],
|
||||||
computed: {
|
computed: {
|
||||||
commands: {
|
commands: {
|
||||||
get() {
|
get() {
|
||||||
return this.modelValue
|
return this.modelValue;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit('update:modelValue', value)
|
this.$emit("update:modelValue", value);
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dragIndex: -1,
|
||||||
|
isDragging: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onDragStart() {
|
||||||
|
this.isDragging = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onDragEnd() {
|
||||||
|
this.isDragging = false;
|
||||||
|
this.dragIndex = -1;
|
||||||
|
},
|
||||||
|
|
||||||
|
onDragOver(event) {
|
||||||
|
if (!this.isDragging) {
|
||||||
|
const rect = event.currentTarget.getBoundingClientRect();
|
||||||
|
const items = this.$el.querySelectorAll('.flow-item');
|
||||||
|
const mouseY = event.clientY;
|
||||||
|
|
||||||
|
// 找到最近的插入位置
|
||||||
|
let closestIndex = -1;
|
||||||
|
let minDistance = Infinity;
|
||||||
|
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
const itemRect = item.getBoundingClientRect();
|
||||||
|
const itemCenter = itemRect.top + itemRect.height / 2;
|
||||||
|
const distance = Math.abs(mouseY - itemCenter);
|
||||||
|
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
closestIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果鼠标在最后一个元素下方,则设置为末尾
|
||||||
|
const lastItem = items[items.length - 1];
|
||||||
|
if (lastItem && mouseY > lastItem.getBoundingClientRect().bottom) {
|
||||||
|
closestIndex = this.commands.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dragIndex = closestIndex;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDragLeave() {
|
||||||
|
if (!this.isDragging) {
|
||||||
|
this.dragIndex = -1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onDrop(event) {
|
onDrop(event) {
|
||||||
const actionData = JSON.parse(event.dataTransfer.getData('action'))
|
const actionData = JSON.parse(event.dataTransfer.getData('action'));
|
||||||
this.$emit('add-command', actionData)
|
const newCommand = {
|
||||||
|
...actionData,
|
||||||
|
id: Date.now(), // 或使用其他方式生成唯一ID
|
||||||
|
argv: '',
|
||||||
|
saveOutput: false,
|
||||||
|
useOutput: null,
|
||||||
|
cmd: actionData.value || actionData.cmd,
|
||||||
|
value: actionData.value || actionData.cmd,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newCommands = [...this.commands];
|
||||||
|
if (this.dragIndex >= 0) {
|
||||||
|
newCommands.splice(this.dragIndex, 0, newCommand);
|
||||||
|
} else {
|
||||||
|
newCommands.push(newCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('update:modelValue', newCommands);
|
||||||
|
this.dragIndex = -1;
|
||||||
|
|
||||||
document.querySelectorAll('.dragging').forEach(el => {
|
document.querySelectorAll('.dragging').forEach(el => {
|
||||||
el.classList.remove('dragging')
|
el.classList.remove('dragging');
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
removeCommand(index) {
|
removeCommand(index) {
|
||||||
const newCommands = [...this.commands]
|
const newCommands = [...this.commands];
|
||||||
newCommands.splice(index, 1)
|
newCommands.splice(index, 1);
|
||||||
this.$emit('update:modelValue', newCommands)
|
this.$emit("update:modelValue", newCommands);
|
||||||
},
|
},
|
||||||
hasOutput(command) {
|
hasOutput(command) {
|
||||||
return commandsWithOutput[command.value] || false
|
return commandsWithOutput[command.value] || false;
|
||||||
},
|
},
|
||||||
canUseOutput(command, index) {
|
canUseOutput(command, index) {
|
||||||
return commandsAcceptOutput[command.value] && this.getAvailableOutputs(index).length > 0
|
return (
|
||||||
|
commandsAcceptOutput[command.value] &&
|
||||||
|
this.getAvailableOutputs(index).length > 0
|
||||||
|
);
|
||||||
},
|
},
|
||||||
getAvailableOutputs(currentIndex) {
|
getAvailableOutputs(currentIndex) {
|
||||||
return this.commands
|
return this.commands
|
||||||
@ -99,67 +180,75 @@ export default defineComponent({
|
|||||||
.map((cmd, index) => ({
|
.map((cmd, index) => ({
|
||||||
label: `${cmd.label} 的输出`,
|
label: `${cmd.label} 的输出`,
|
||||||
value: index,
|
value: index,
|
||||||
disable: !cmd.saveOutput
|
disable: !cmd.saveOutput,
|
||||||
}))
|
}))
|
||||||
.filter(item => !item.disable)
|
.filter((item) => !item.disable);
|
||||||
},
|
},
|
||||||
toggleSaveOutput(index) {
|
toggleSaveOutput(index) {
|
||||||
const newCommands = [...this.commands]
|
const newCommands = [...this.commands];
|
||||||
newCommands[index].saveOutput = !newCommands[index].saveOutput
|
newCommands[index].saveOutput = !newCommands[index].saveOutput;
|
||||||
if (!newCommands[index].saveOutput) {
|
if (!newCommands[index].saveOutput) {
|
||||||
newCommands.forEach((cmd, i) => {
|
newCommands.forEach((cmd, i) => {
|
||||||
if (i > index && cmd.useOutput === index) {
|
if (i > index && cmd.useOutput === index) {
|
||||||
cmd.useOutput = null
|
cmd.useOutput = null;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
this.$emit('update:modelValue', newCommands)
|
this.$emit("update:modelValue", newCommands);
|
||||||
},
|
},
|
||||||
handleArgvChange(index, value) {
|
handleArgvChange(index, value) {
|
||||||
const newCommands = [...this.commands]
|
const newCommands = [...this.commands];
|
||||||
newCommands[index].argv = value
|
newCommands[index].argv = value;
|
||||||
this.$emit('update:modelValue', newCommands)
|
this.$emit("update:modelValue", newCommands);
|
||||||
},
|
},
|
||||||
handleUseOutputChange(index, value) {
|
handleUseOutputChange(index, value) {
|
||||||
const newCommands = [...this.commands]
|
const newCommands = [...this.commands];
|
||||||
newCommands[index].useOutput = value
|
newCommands[index].useOutput = value;
|
||||||
if (value !== null) {
|
if (value !== null) {
|
||||||
newCommands[index].argv = ''
|
newCommands[index].argv = "";
|
||||||
}
|
}
|
||||||
this.$emit('update:modelValue', newCommands)
|
this.$emit("update:modelValue", newCommands);
|
||||||
},
|
},
|
||||||
getPlaceholder(element, index) {
|
getPlaceholder(element, index) {
|
||||||
if (element.useOutput !== null) {
|
if (element.useOutput !== null) {
|
||||||
return `使用 ${this.commands[element.useOutput].label} 的输出`
|
return `使用 ${this.commands[element.useOutput].label} 的输出`;
|
||||||
}
|
}
|
||||||
return element.desc
|
return element.desc;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.composer-flow {
|
.composer-flow {
|
||||||
background-color: white;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-flow-container {
|
.command-flow-container {
|
||||||
min-height: 100px;
|
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-flow-container:empty {
|
.body--dark .command-flow-container {
|
||||||
border: 2px dashed #e0e0e0;
|
background-color: #303132;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-list {
|
.flow-list {
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drop-area {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-flow {
|
.empty-flow {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -169,6 +258,11 @@ export default defineComponent({
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body--dark .empty-flow {
|
||||||
|
border: 2px dashed #676666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-flow:hover {
|
.empty-flow:hover {
|
||||||
@ -176,12 +270,22 @@ export default defineComponent({
|
|||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body--dark .empty-flow:hover {
|
||||||
|
border-color: #676666;
|
||||||
|
background-color: #303132;
|
||||||
|
}
|
||||||
|
|
||||||
/* 拖拽时的视觉反馈 */
|
/* 拖拽时的视觉反馈 */
|
||||||
.command-flow-container.drag-over {
|
.command-flow-container.drag-over {
|
||||||
background-color: #f0f4ff;
|
background-color: #f0f4ff;
|
||||||
border-color: #2196f3;
|
border-color: #2196f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body--dark .command-flow-container.drag-over {
|
||||||
|
background-color: #303132;
|
||||||
|
border-color: #676666;
|
||||||
|
}
|
||||||
|
|
||||||
/* 滑动淡出动画 */
|
/* 滑动淡出动画 */
|
||||||
.slide-fade-enter-active,
|
.slide-fade-enter-active,
|
||||||
.slide-fade-leave-active {
|
.slide-fade-leave-active {
|
||||||
@ -198,7 +302,88 @@ export default defineComponent({
|
|||||||
transform: translateX(30px);
|
transform: translateX(30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 拖拽指示器基础样式 */
|
||||||
|
.flow-item::before,
|
||||||
|
.flow-item::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
right: 12px;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
rgba(0, 0, 0, 0.08) 10%,
|
||||||
|
rgba(0, 0, 0, 0.15) 50%,
|
||||||
|
rgba(0, 0, 0, 0.08) 90%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleX(0.95) translateY(0);
|
||||||
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
pointer-events: none;
|
||||||
|
filter: blur(0.2px);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item::before {
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item::after {
|
||||||
|
bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 激活状态 - 插入到元素之前 */
|
||||||
|
.flow-item.insert-before::before {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scaleX(1) translateY(0);
|
||||||
|
box-shadow:
|
||||||
|
0 0 10px rgba(0, 0, 0, 0.03),
|
||||||
|
0 0 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 激活状态 - 插入到最后 */
|
||||||
|
.flow-item.insert-after::after {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scaleX(1) translateY(0);
|
||||||
|
box-shadow:
|
||||||
|
0 0 10px rgba(0, 0, 0, 0.03),
|
||||||
|
0 0 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拖拽时的卡片效果 */
|
||||||
.flow-item {
|
.flow-item {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item.insert-before {
|
||||||
|
transform: translateY(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-item.insert-after {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拖拽时相邻元素的间距调整 */
|
||||||
|
.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);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -2,9 +2,14 @@
|
|||||||
<div class="composer-list">
|
<div class="composer-list">
|
||||||
<q-list bordered separator class="rounded-borders">
|
<q-list bordered separator class="rounded-borders">
|
||||||
<template v-for="category in commandCategories" :key="category.label">
|
<template v-for="category in commandCategories" :key="category.label">
|
||||||
<q-item-label header class="q-py-sm bg-grey-2">
|
<q-item-label header class="q-py-sm">
|
||||||
<div class="row items-center">
|
<div class="row items-center">
|
||||||
<q-icon :name="category.icon" color="primary" size="sm" class="q-mr-sm" />
|
<q-icon
|
||||||
|
:name="category.icon"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
class="q-mr-sm"
|
||||||
|
/>
|
||||||
<span class="text-weight-medium">{{ category.label }}</span>
|
<span class="text-weight-medium">{{ category.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
@ -17,8 +22,9 @@
|
|||||||
@dragstart="onDragStart($event, command)"
|
@dragstart="onDragStart($event, command)"
|
||||||
>
|
>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label class="text-weight-medium">{{ command.label }}</q-item-label>
|
<q-item-label class="text-weight-medium">{{
|
||||||
<q-item-label caption>{{ command.desc }}</q-item-label>
|
command.label
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon name="drag_indicator" color="grey-6" size="16px" />
|
<q-icon name="drag_indicator" color="grey-6" size="16px" />
|
||||||
@ -30,61 +36,76 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from "vue";
|
||||||
import { commandCategories } from './composerConfig'
|
import { commandCategories } from "./composerConfig";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ComposerList',
|
name: "ComposerList",
|
||||||
props: {
|
props: {
|
||||||
commands: {
|
commands: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
commandCategories
|
commandCategories,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getCategoryCommands(category) {
|
getCategoryCommands(category) {
|
||||||
return this.commands.filter(cmd =>
|
return this.commands.filter((cmd) =>
|
||||||
category.commands.some(catCmd => catCmd.value === cmd.value || catCmd.value === cmd.cmd)
|
category.commands.some(
|
||||||
)
|
(catCmd) => catCmd.value === cmd.value || catCmd.value === cmd.cmd
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onDragStart(event, command) {
|
onDragStart(event, command) {
|
||||||
event.dataTransfer.setData('action', JSON.stringify({
|
event.dataTransfer.setData(
|
||||||
...command,
|
"action",
|
||||||
cmd: command.value || command.cmd
|
JSON.stringify({
|
||||||
}))
|
...command,
|
||||||
event.target.classList.add('dragging')
|
cmd: command.value || command.cmd,
|
||||||
event.target.addEventListener('dragend', () => {
|
})
|
||||||
event.target.classList.remove('dragging')
|
);
|
||||||
}, { once: true })
|
event.target.classList.add("dragging");
|
||||||
}
|
event.target.addEventListener(
|
||||||
}
|
"dragend",
|
||||||
})
|
() => {
|
||||||
|
event.target.classList.remove("dragging");
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.composer-list {
|
.composer-list {
|
||||||
background-color: white;
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-item {
|
.body--dark .composer-list {
|
||||||
border: 1px solid #e0e0e0;
|
background-color: rgba(32, 32, 32, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-item.q-item-type {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 4px 8px;
|
margin: 4px 8px;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
background: white;
|
background: rgba(255, 255, 255, 0.8);
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-item:hover {
|
.command-item.q-item-type:hover {
|
||||||
background-color: #f8f9fa;
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
transform: translateX(4px);
|
transform: translateX(4px);
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-item.dragging {
|
.command-item.dragging {
|
||||||
@ -97,22 +118,22 @@ export default defineComponent({
|
|||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
border-bottom: 1px solid #e0e0e0;
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
background-color: #f5f5f5;
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 暗色模式适配 */
|
/* 暗色模式适配 */
|
||||||
.body--dark .command-item {
|
.body--dark .command-item.q-item-type {
|
||||||
border-color: #424242;
|
border-color: rgba(255, 255, 255, 0.05);
|
||||||
background: #1d1d1d;
|
background: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.body--dark .command-item:hover {
|
.body--dark .command-item.q-item-type:hover {
|
||||||
background-color: #2d2d2d;
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.body--dark .q-item-label.header {
|
.body--dark .q-item-label.header {
|
||||||
border-color: #424242;
|
border-color: rgba(255, 255, 255, 0.05);
|
||||||
background-color: #2d2d2d;
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user